diff --git a/CMake/BuildConfigurations/All.cmake b/CMake/BuildConfigurations/All.cmake index 6a3b24125e..2e65fd8460 100644 --- a/CMake/BuildConfigurations/All.cmake +++ b/CMake/BuildConfigurations/All.cmake @@ -1,45 +1,44 @@ set(MITK_CONFIG_PACKAGES ) set(_apple_package_excludes) set(_package_excludes ${_apple_package_excludes} OpenCL OpenMP SYSTEM_Boost Boost_LIBRARIES SYSTEM_PYTHON SUPERBUILD POLHEMUS_TRACKER MICROBIRD_TRACKER MICROBIRD_TRACKER_INCLUDE_DIR MICROBIRD_TRACKER_LIB MICRON_TRACKER OPTITRACK_TRACKER SPACENAVIGATOR TOF_KINECT TOF_KINECTV2 TOF_MESASR4000 TOF_PMDCAMBOARD TOF_PMDCAMCUBE TOF_PMDO3 US_TELEMED_SDK videoInput - WIIMOTE ) get_cmake_property(_cache_vars CACHE_VARIABLES) foreach(_cache_var ${_cache_vars}) string(REGEX REPLACE "MITK_USE_(.+)" "\\1" _package "${_cache_var}") if(_package AND NOT _package STREQUAL _cache_var) list(FIND _package_excludes ${_package} _index) if(_index EQUAL -1) list(APPEND MITK_CONFIG_PACKAGES ${_package}) endif() endif() endforeach() set(MITK_BUILD_ALL_APPS ON CACHE BOOL "Build all MITK applications" FORCE) set(MITK_BUILD_ALL_PLUGINS ON CACHE BOOL "Build all MITK plugins" FORCE) set(MITK_BUILD_EXAMPLES ON CACHE BOOL "Build the MITK examples" FORCE) set(BLUEBERRY_BUILD_ALL_PLUGINS ON CACHE BOOL "Build all BlueBerry plugins" FORCE) diff --git a/CMake/Whitelists/FlowBenchSegmentation.cmake b/CMake/Whitelists/FlowBenchSegmentation.cmake index 60c1587601..c0eedb2dfb 100644 --- a/CMake/Whitelists/FlowBenchSegmentation.cmake +++ b/CMake/Whitelists/FlowBenchSegmentation.cmake @@ -1,55 +1,54 @@ set(enabled_modules Core CppMicroServices DICOM DICOMPM DataTypesExt AlgorithmsExt DICOMQI Multilabel SceneSerializationBase DICOMPMIO DICOMImageIO ContourModel DICOMSegIO LegacyGL MapperExt SceneSerialization LegacyIO IOExt MultilabelIO AppUtil QtWidgets QtWidgetsExt Segmentation SegmentationUI PlanarFigure Annotation SurfaceInterpolation GraphAlgorithms ImageExtraction ImageStatistics ) set(enabled_plugins org.blueberry.core.commands org.blueberry.core.expressions org.blueberry.core.runtime org.blueberry.ui.qt org.blueberry.ui.qt.help org.blueberry.ui.qt.log -org.mitk.core.ext org.mitk.core.services org.mitk.gui.common org.mitk.gui.qt.application org.mitk.gui.qt.common org.mitk.gui.qt.datamanager org.mitk.gui.qt.ext org.mitk.gui.qt.flow.segmentation org.mitk.gui.qt.flowapplication org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.planarfigure ) diff --git a/CMake/manifest.xml.in b/CMake/manifest.xml.in index 1c020827f9..09d3679994 100644 --- a/CMake/manifest.xml.in +++ b/CMake/manifest.xml.in @@ -1,10 +1,10 @@ UTF-8 - PerMonitorV2 + PerMonitorV2 diff --git a/Documentation/Doxygen/3-DeveloperManual/Application/DevelopmentApplication.dox b/Documentation/Doxygen/3-DeveloperManual/Application/DevelopmentApplication.dox index baf97ed86f..587d9c2803 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Application/DevelopmentApplication.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Application/DevelopmentApplication.dox @@ -1,27 +1,22 @@ /** \page DevelopmentApplication Developing with the MITK Application Framework MITK offers a powerful application featuring a plugin system and many predefined plugins. You can configure this application to offer a set of functionality to the user and easily create an installer. Working with the application, you will have to learn about extension points and the Blueberry framework itself. If you are new to the matter, please also consult \ref Architecture and \ref FirstSteps. The BlueBerry framework extension-point reference is available here: - \subpage BlueBerryExtPointsIndex - -The MITK application extension-point reference is available here: - - - \subpage mitkExtPointsIndex - */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Application/ExtensionPointReference.dox b/Documentation/Doxygen/3-DeveloperManual/Application/ExtensionPointReference.dox deleted file mode 100644 index e324ea7885..0000000000 --- a/Documentation/Doxygen/3-DeveloperManual/Application/ExtensionPointReference.dox +++ /dev/null @@ -1,64 +0,0 @@ -/** - -\page mitkExtPointsIndex MITK Extension-Point Reference - -- \subpage mitkExtPointsIndex_InputDevices - -\page mitkExtPointsIndex_InputDevices Input Devices - -\tableofcontents - -\section mitkExtPointsIndex_InputDevices_Identifier Identifier - -\c org.mitk.core.ext.inputdevices - -\section mitkExtPointsIndex_InputDevices_Description Description - -This extension point is used to define additional input devices. - -\section mitkExtPointsIndex_InputDevices_ConfigurationMarkup Configuration Markup - -\code{.unparsed} - - -\endcode - -- point: a fully qualified identifier of the target extension point -- id: an optional identifier of the extension instance -- name: an optional name of the extension instance - -\code{.unparsed} - - -\endcode - -- id: the identifier of the input device -- name: an optional name of the input device -- class: a fully qualified name of the class that implements mitk::IInputDevice. - -\code{.unparsed} - -\endcode - -An optional subelement whose body should contain text providing a short description of the input device. - -\section mitkExtPointsIndex_InputDevices_Examples Examples - -\code{.unparsed} - - - My new 20-dimensional input device - - -\endcode - -*/ diff --git a/Documentation/Doxygen/3-DeveloperManual/DeveloperManualPortal.dox b/Documentation/Doxygen/3-DeveloperManual/DeveloperManualPortal.dox index 678ce379c8..8ab74c6556 100644 --- a/Documentation/Doxygen/3-DeveloperManual/DeveloperManualPortal.dox +++ b/Documentation/Doxygen/3-DeveloperManual/DeveloperManualPortal.dox @@ -1,31 +1,28 @@ /** \developersguidemainpage{DeveloperManualPortal} Developer Manual Development with MITK can happen under several conditions. Depending on whether you are using the Toolkit or the entire application, different sections may apply to you. In case you are unsure about what you need, please refer to \link Architecture The Architecture of MITK text\endlink. An extensive Introduction to MITK is available under \link StartingDevelopment Starting your MITK Development\endlink. Once you have made yourself familiar with MITK, you should have a look at the \link Concepts Development Concepts\endlink, as MITK implements a lot of high-level functionality. Knowing about these concepts will prevent you from reimplementing functionality. Once you start consuming more specific functionality, the \link MITKModuleManualsListPage Module Manual\endlink will be helpful to understand how a specific plugin works and what functionality it provides. */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox index eb1e126554..475dd319e3 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox @@ -1,44 +1,44 @@ /** \page CMAKE_FAQ CMake FAQ \section CMAKE_FAQ_General A general comment MITK uses %CMake for configuration. If you want to develop either using MITK as a toolkit or by extending the capabilities of the applications provided by us, we recommend using %CMake for your own project too. While it might be possible to use MITK in conjunction with other options, such as QMake or setting up your own project manually it will invariably involve a lot of work and probably hacks as well. As we do have no experience with this, we will not be able to help you. Be prepared to do a lot of tweaking on by yourself. This guide does not try to give a general introduction to CMake, instead it tries to answer some basic questions that might arise for those new to %CMake, to help you get started on MITK. For a more comprehensive introduction on %CMake see here. We will assume in this guide, that the path to your source is /MITK/. \section CMAKE_FAQ_Question Basic Questions \subsection CMAKE_FAQ_Question_WhereGetIt Where do I get CMake and what version do I need? See \ref BuildInstructions_Prerequisites. \subsection CMAKE_FAQ_Question_NewPluginNothing I coded a new plugin for the Workbench and nothing happened. Why? Do note that you need to move the source to the Plugins directory and you will have to add the plugin to the config file (most likely Plugins/PluginList.cmake). After that see \ref CMAKE_FAQ_Question_HowDoIActivatePlugin. \subsection CMAKE_FAQ_Question_HowDoIActivatePlugin I want to use a plugin, how do I activate it?
  1. Start %CMake in the `MITK-build` directory inside your superbuild folder. E.g. `cd ~//MITK-build` `ccmake .` or `cmake-gui .`
  2. Optional: *Configure* to see all %CMake variables.
  3. Find the variable `MITK_BUILD_` and activate it. The `` refers to the package name of the plugin.
    - E.g. the plugin-id (also, the package name) `org.mitk.core.ext` is the sub-directory of same name in plugins directory (`~/MITK/Plugins/`). + E.g. the plugin-id (also, the package name) `org.mitk.gui.qt.application` is the sub-directory of same name in plugins directory (`~/MITK/Plugins/`).
  4. *Configure*, again
  5. *Generate*
  6. Rebuild the `MITK-build` target using your development environment.
\subsection CMAKE_FAQ_Question_HowDoIActivateModule I want to use a module, how do I activate it? Modules are build automatically if a plugin that requires them is activated. See \ref CMAKE_FAQ_Question_HowDoIActivatePlugin. \subsection CMAKE_FAQ_Question_HowOwnToolkits MITK always downloads the toolkits, but I want to use my own. This is covered in \ref HowToNewProjectCustomizingMITKConfigure. \subsection CMAKE_FAQ_Question_HowOwnProjectMITK I want to use an MITK plugin in my own project but I can not find it. See \ref HowToNewProjectAddingMITKFunctionality. */ diff --git a/Examples/Plugins/org.mitk.example.gui.customviewer/manifest_headers.cmake b/Examples/Plugins/org.mitk.example.gui.customviewer/manifest_headers.cmake index 058dcf0b91..170a057a2d 100644 --- a/Examples/Plugins/org.mitk.example.gui.customviewer/manifest_headers.cmake +++ b/Examples/Plugins/org.mitk.example.gui.customviewer/manifest_headers.cmake @@ -1,9 +1,8 @@ set(Plugin-Name "MITK Example Custom Viewer") set(Plugin-Version "1.0.0") set(Plugin-Vendor "German Cancer Research Center (DKFZ)") set(Plugin-ContactAddress "https://www.mitk.org") set(Require-Plugin - org.mitk.core.ext # Registers file reader factories org.mitk.gui.qt.application # Initializes GlobalInteraction and registers MITK Core factories ) diff --git a/Modules/Classification/CLVigraRandomForest/test/files.cmake b/Modules/Classification/CLVigraRandomForest/test/files.cmake index 3f04b16d08..9a4351245d 100644 --- a/Modules/Classification/CLVigraRandomForest/test/files.cmake +++ b/Modules/Classification/CLVigraRandomForest/test/files.cmake @@ -1,3 +1,5 @@ -set(MODULE_TESTS - mitkVigraRandomForestTest.cpp -) +set(MODULE_TESTS "") + +if(NOT APPLE) + list(APPEND MODULE_TESTS mitkVigraRandomForestTest.cpp) +endif() diff --git a/Modules/Core/include/mitkInteractionConst.h b/Modules/Core/include/mitkInteractionConst.h index e83f33b429..0df91c3715 100644 --- a/Modules/Core/include/mitkInteractionConst.h +++ b/Modules/Core/include/mitkInteractionConst.h @@ -1,776 +1,762 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkInteractionConst_h #define mitkInteractionConst_h //##Documentation //## @file mitkInteractionConst.h //## @brief Constants for most interaction classes, due to the generic StateMachines. //## //## Changes in Type, ButtonState or Key has to be don in mitkEventMapper.cpp, too. //## @ingroup Interaction /*Prefixes for Constants: E = Enumeration EID = EventId's Op = Operations Ac = Action Type_ = Type of Event BS_ = ButtonStates and Buttons Key_ = Keys like in QT */ namespace mitk { // Constants for EventIds; use the according constant to through an event in the code enum EEventIds { EIDNULLEVENT = 0, EIDLEFTMOUSEBTN = 1, EIDRIGHTMOUSEBTN = 2, EIDLEFTMOUSEBTNANDSHIFT = 3, EIDMIDDLEMOUSEBTN = 4, EIDLEFTMOUSEBTNANDCTRL = 5, EIDMIDDLEMOUSEBTNANDCTRL = 6, EIDRIGHTMOUSEBTNANDCTRL = 7, EIDLEFTMOUSEBTNDOUBLECLICK = 8, EIDMOUSEWHEEL = 9, EIDLEFTMOUSERELEASE = 505, EIDMIDDLEMOUSERELEASE = 506, EIDRIGHTMOUSERELEASE = 507, EIDLEFTMOUSERELEASEANDSHIFT = 508, EIDMOUSEMOVE = 520, EIDLEFTMOUSEBTNANDMOUSEWHEEL = 521, EIDRIGHTMOUSEBTNANDMOUSEWHEEL = 522, EIDMIDDLEMOUSEBTNANDMOUSEWHEEL = 523, EIDLEFTMOUSEBTNANDMOUSEMOVE = 530, EIDRIGHTMOUSEBTNANDMOUSEMOVE = 531, EIDMIDDLEMOUSEBTNANDMOUSEMOVE = 533, EIDCTRLANDLEFTMOUSEBTNANDMOUSEMOVE = 534, EIDCTRLANDRIGHTMOUSEBTNANDMOUSEMOVE = 535, EIDCTRLANDMIDDLEMOUSEBTNANDMOUSEMOVE = 536, EIDCTRLANDLEFTMOUSEBTNRELEASE = 537, EIDCTRLANDRIGHTMOUSEBTNRELEASE = 538, EIDCTRLANDMIDDLEMOUSEBTNRELEASE = 539, EIDSHIFTANDCTRLANDMIDDLEMOUSEBTN = 540, EIDSHIFTANDLEFTMOUSEBTNANDMOUSEMOVE = 541, EIDSHIFTANDCTRLANDMOUSEMOVE = 542, EIDSHIFTANDCTRLANDMOUSERELEASE = 543, EIDALTANDLEFTMOUSEBTN = 600, EIDALTANDLEFTMOUSEBTNANDMOUSEMOVE = 610, EIDALTANDLEFTMOUSERELEASE = 620, EIDCTRLANDLEFTMOUSEWHEEL = 630, EIDALTANDMOUSEWHEEL = 640, EIDALTANDMIDDLEMOUSEBTN = 641, EIDALTANDMIDDLEMOUSEBTNANDMOVE = 642, EIDALTANDMIDDLEMOUSEBTNRELEASE = 643, EIDALTANDSHIFTANDRIGHTMOUSEBTN = 644, EIDALTANDSHIFTANDRIGHTMOUSEBTNANDMOUSEMOVE = 645, EIDALTANDSHIFTANDRIGHTMOUSEBTNRELEASE = 646, EIDSHIFTANDRIGHTMOUSEPRESS = 2000, EIDSHIFTANDRIGHTMOUSEMOVE = 2001, EIDSHIFTANDRIGHTMOUSERELEASE = 2002, EIDSHIFTANDMIDDLEMOUSEPRESS = 2003, EIDSHIFTANDMIDDLEMOUSEMOVE = 2004, EIDSHIFTANDMIDDLEMOUSERELEASE = 2005, - EIDSPACENAVIGATORINPUT = 4001, // 3d Mouse, SpaceNavigator input - EIDSPACENAVIGATORKEYDOWN = 4002, // 3d Mouse, KeyDown - EIDWIIMOTEINPUT = 4003, // WiiMote input - EIDWIIMOTEBUTTON = 4004, // WiiMote home button - EIDWIIMOTEBUTTONB = 4005, // WiiMote b button EIDSTRGANDN = 10, EIDSTRGANDE = 11, EIDDELETE = 12, EIDN = 13, EIDESCAPE = 14, EIDP = 15, EIDR = 16, EIDT = 17, EIDS = 18, EIDE = 19, EIDSTRGANDALTANDA = 20, EIDSTRGANDALTANDB = 21, EIDH = 22, EIDRETURN = 23, EIDENTER = 24, EIDSPACE = 25, EIDPLUS = 26, EIDMINUS = 27, EIDSTRGANDALTANDH = 30, EIDSTRGANDALTANDI = 31, EIDSTRGANDALTANDS = 40, EIDALT = 90, EIDSTRGANDB = 91, EIDNEW = 1000, EIDOLD = 1001, EIDFINISHED = 1002, EIDNO = 1003, EIDYES = 1004, EIDSAME = 1005, EIDNOANDLASTOBJECT = 1006, EIDNOANDNOTLASTOBJECT = 1007, EIDLAST = 1008, EIDNOTLAST = 1009, EIDSTSMALERNMINUS1 = 1010, EIDSTLARGERNMINUS1 = 1011, EIDPOSITIONEVENT = 1012, EIDEDIT = 1013, EIDSMALLERN = 1014, EIDEQUALSN = 1015, EIDLARGERN = 1016, EIDEMPTY = 1017, EIDSUBDESELECT = 1020, EIDSMTOSELECTED = 1030, EIDSMTODESELECTED = 1031, EIDTIP = 1050, EIDHEAD = 1051, EIDBODY = 1052, EIDCLEAR = 1100, EIDACTIVATETOOL = 1300, EIDPRINT = 3001, EV_INIT = 5551001, EV_PREVIOUS = 5551002, EV_PATH_COLLECTION_SELECTED = 5551003, EV_NAVIGATION_SELECTED = 5551004, EV_LESS_THEN_MIN_COUNT = 5551005, EV_READY = 5551006, EV_NEXT = 5551007, EV_DONE = 5551008, EV_NEW_LANDMARK = 5551009, EV_REMOVE_LANDMARK = 5551010, EIDINSIDE = 2500, EIDA = 4001, EIDB = 4002, EIDC = 4003, EIDD = 4004, EIDF = 4005, EIDG = 4006, EIDI = 4007, EIDJ = 4008, EIDK = 4009, EIDL = 4010, EIDM = 4011, EIDO = 4012, EIDQ = 4013, EIDU = 4014, EIDV = 4015, EIDW = 4016, EIDX = 4017, EIDY = 4018, EIDZ = 4019, EID1 = 4020, EID2 = 4021, EID3 = 4022, EID4 = 4023, EID5 = 4024, EID6 = 4025, EID7 = 4026, EID8 = 4027, EID9 = 4028, EID0 = 4029, EIDFIGUREHOVER = 12340, EIDNOFIGUREHOVER = 12341 }; //##Constants for Operations - //## xomments are always examples of the usage + //## comments are always examples of the usage enum EOperations { OpNOTHING = 0, OpTEST = 1, OpNEWCELL = 10, // add a new cell OpADD = 100, // add a point or a vessel OpUNDOADD = 101, OpADDLINE = 1001, // add a line OpINSERT = 200, // insert a point at position OpINSERTLINE = 201, // insert a line at position OpINSERTPOINT = 202, OpCLOSECELL = 250, // close a cell (to a polygon) OpOPENCELL = 251, // close a cell (to a polygon) OpMOVE = 300, // move a point OpMOVELINE = 301, // move a line OpMOVECELL = 302, // move a line OpUNDOMOVE = 303, OpMOVEPOINTUP = 304, OpMOVEPOINTDOWN = 305, OpREMOVE = 400, // remove a point at position OpREMOVELINE = 401, // remove a line at position OpREMOVECELL = 402, // remove a cell OpREMOVEPOINT = 403, OpDELETE = 500, // delete OpDELETELINE = 501, // delete the last line in a cell OpUNDELETE = 502, OpDELETECELL = 505, OpSTATECHANGE = 600, // change a state OpTIMECHANGE = 601, // change a state OpTERMINATE = 666, // change a state OpSELECTPOINT = 700, OpSELECTLINE = 701, OpSELECTCELL = 702, OpSELECTSUBOBJECT = 703, // for VesselGraphInteractor // OpSELECTNEWSUBOBJECT = 704, //for VesselGraphInteractor OpSELECT = 705, OpDESELECTPOINT = 800, OpDESELECTLINE = 801, OpDESELECTCELL = 802, OpDESELECTSUBOBJECT = 803, // for VesselGraphInteractor OpDESELECTALL = 804, // for VesselGraphInteractor OpDESELECT = 805, OpNAVIGATE = 900, OpZOOM = 1000, OpSCALE = 1100, OpROTATE = 1200, OpORIENT = 1201, OpRESTOREPLANEPOSITION = 1202, OpAPPLYTRANSFORMMATRIX = 1203, OpSETPOINTTYPE = 1210, OpMODECHANGE = 1500, OpSENDCOORDINATES = 1600, OpPERIPHERYSEARCH = 2000, // used in VesselGraphInteractor OpROOTSEARCH = 2001, // used in VesselGraphInteractor OpTHICKSTVESSELSEARCH = 2002, // used in VesselGraphInteractor OpSHORTESTPATHSEARCH = 2003, // used in VesselGraphInteractor OpATTRIBUTATION = 2004, // used in VesselGraphInteractor OpDEFAULT = 2006, // used in VesselGraphInteractor OpSURFACECHANGED = 3000, // used for changing polydata in surfaces }; //##Constants for EventMapping... //##connects the statemachine.xml-File with the implemented conditions. //##within one statemachine the choice of the actionconstants is freely //## //## ActionId enum EActions { AcDONOTHING = 0, AcINITNEWOBJECT = 5, AcINITEDITOBJECT = 6, AcINITEDITGROUP = 7, AcINITMOVEMENT = 8, AcINITMOVE = 9, AcINITFOREGROUND = 45, // used in SeedsInteractor for setting the foreground seeds AcINITBACKGROUND = 46, // used in SeedsInteractor for setting the background seeds AcINITNEUTRAL = 47, // used in SeedsInteractor for setting the neutral seeds (rubber) AcINITUPDATE = 1235, // For shape model deformation AcADDPOINT = 10, AcADDPOINTRMB = 6000, // in mitralPointSetInteractor used to set a different type of point AcADD = 11, AcADDLINE = 12, AcADDANDFINISH = 13, AcADDSELECTEDTOGROUP = 64, AcCHECKPOINT = 21, AcCHECKLINE = 22, AcCHECKCELL = 23, AcCHECKELEMENT = 30, // check if there is a element close enough (picking) AcCHECKOBJECT = 31, // check if an object is hit AcCHECKNMINUS1 = 32, // check if the number of elements is equal to N-1 AcCHECKEQUALS1 = 33, // check if the number of elements in the data is equal to 1 AcCHECKNUMBEROFPOINTS = 330, // check the number of elements in the data AcCHECKSELECTED = 34, // check if the given element is selected or not AcCHECKONESELECTED = 340, // check if there is an element that is selected AcCHECKHOVERING = 341, // check if there is an element that is selected AcCHECKGREATERZERO = 35, // check if the current number of elements is greater than 0 AcCHECKGREATERTWO = 36, // check if the current number of elements is greater than two AcCHECKOPERATION = 37, // check if the operation is of one spectial type AcCHECKONESUBINTERACTOR = 38, AcCHECKSUBINTERACTORS = 39, AcFINISHOBJECT = 40, AcFINISHGROUP = 41, AcFINISHMOVEMENT = 42, AcFINISHMOVE = 43, AcFINISH = 44, AcSEARCHOBJECT = 50, AcSEARCHGROUP = 51, AcSEARCHANOTHEROBJECT = 52, // one object is selected and another object is to be added to selection AcSELECTPICKEDOBJECT = 60, // select the picked object and deselect others AcSELECTANOTHEROBJECT = 61, AcSELECTGROUP = 62, AcSELECTALL = 63, AcSELECT = 65, AcSELECTPOINT = 66, AcSELECTLINE = 68, AcSELECTCELL = 67, AcSELECTSUBOBJECT = 69, // used in VesselGraphInteractor AcDESELECTOBJECT = 70, // deselect picked from group AcDESELECTALL = 72, AcDESELECT = 75, AcDESELECTPOINT = 76, AcDESELECTLINE = 78, AcDESELECTCELL = 77, AcNEWPOINT = 80, AcNEWSUBOBJECT = 81, AcMOVEPOINT = 90, AcMOVESELECTED = 91, AcMOVE = 92, AcMOVEPOINTUP = 93, AcMOVEPOINTDOWN = 94, AcREMOVEPOINT = 100, AcREMOVE = 101, AcREMOVELINE = 102, AcREMOVEALL = 103, AcREMOVESELECTEDSUBOBJECT = 104, // used in VesselGraphInteractor AcWHEEL = 105, AcPLUS = 106, AcMINUS = 107, AcDELETEPOINT = 120, AcCLEAR = 130, // clear all elements from a list AcINSERTPOINT = 110, AcINSERTLINE = 111, AC_SET_NEXT_BUTTON_VISIBLE = 5550001, AC_SET_NEXT_BUTTON_INVISIBLE = 5550002, AC_SET_PREVIOUS_BUTTON_VISIBLE = 5550003, AC_SET_PREVIOUS_BUTTON_INVISIBLE = 5550004, AC_SET_ASSISTAND_WIDGET_STECK = 5550005, AC_SETMAX_COUNT_REF_POINTS = 5550006, AC_SET_NEXT_BUTTON_TEXT = 5550007, AC_CHECK_LANDMARK_COUNT = 5550008, AC_SET_DONE_FALSE = 5550009, AC_INIT = 55500010, AC_SET_APPLICATION_SELECTED_FALSE = 55500011, AC_SENSOR_ATTACHED = 55500012, AC_CLOSE_ASSISTENT = 55500013, AC_START_APPLICATION_TEXT = 55500014, AC_START_NAVIGATION = 55500015, AC_START_PATHCOLLECTION = 55500016, AC_LOAD_LANDMARKS = 55500017, AC_CALCULATE_LANDMARK_TRANSFORM = 55500018, AcTERMINATE_INTERACTION = 666, AcTRANSLATESTART = 1000, AcTRANSLATE = 1001, AcSCALESTART = 1002, AcSCALE = 1003, AcROTATESTART = 1004, AcROTATE = 1005, AcINITAFFINEINTERACTIONS = 1006, AcFINISHAFFINEINTERACTIONS = 1007, AcTRANSLATEEND = 1008, AcSCALEEND = 1009, AcROTATEEND = 1010, AcINITZOOM = 1011, AcZOOM = 1012, AcSCROLL = 1013, AcLEVELWINDOW = 1014, AcSCROLLMOUSEWHEEL = 1015, AcSETSTARTPOINT = 1050, AcMODEDESELECT = 1100, // set interactor in not selected mode AcMODESELECT = 1101, // set interactor in selected mode AcMODESUBSELECT = 1102, // set interacor in sub selected mode AcINFORMLISTENERS = 1200, AcASKINTERACTORS = 1201, AcCHECKGREATERONE = 1500, AcCHECKBOUNDINGBOX = 1510, AcFORCESUBINTERACTORS = 1550, AcSENDCOORDINATES = 1600, AcTRANSMITEVENT = 2000, // to transmit an event to a lower Interactor/Statemachine AcPERIPHERYSEARCH = 3000, // used in VesselGraphInteractor AcROOTSEARCH = 3001, // used in VesselGraphInteractor AcTHICKSTVESSELSEARCH = 3002, // used in VesselGraphInteractor AcSHORTESTPATHSEARCH = 3003, // used in VesselGraphInteractor AcSINGLE = 3004, // used in VesselGraphInteractor AcATTRIBUTATION = 3005, // used in VesselGraphInteractor AcDEFAULT = 3007, // used in VesselGraphInteractor AcSETVESSELELEMENT = 3008, // used in VesselGraphInteractor AcCHECKBARRIERSTATUS = 3010, // used in VesselGraphInteractor AcUPDATEMESH = 1234, // For Shape Model Interaction AcINCREASE = 49012, AcDECREASE = 49013, AcMODIFY = 49014, AcUNDOUPDATE = 1236, // For restoring a mesh after an update AcENTEROBJECT = 48000, AcLEAVEOBJECT = 48001, AcSWITCHOBJECT = 48002, AcUPDATELINE = 48003, AcINITLINE = 48004, AcTERMINATELINE = 48005, AcCREATEBOX = 48006, AcCREATEOBJECTFROMLINE = 48007, AcCANCEL = 48008, AcACTIVATETOOL = 48009, AcROTATEAROUNDPOINT1 = 49002, AcROTATEAROUNDPOINT2 = 49003, AcMOVEPOINT1 = 49004, AcMOVEPOINT2 = 49005, AcUPDATEPOINT = 49006, AcUPDATERADIUSMOUSEWHEEL = 49007, AcDISPLAYOPTIONS = 49009, AcCYCLE = 49010, AcACCEPT = 49011, - AcONSPACENAVIGATORMOUSEINPUT = 4001, // On input of 3D Mouse - AcONPACENAVIGATORKEYDOWN = 4002, // On input of 3D Mouse - AcONWIIMOTEINPUT = 4003, // used for wiimote to signal IR input - AcRESETVIEW = 4004, // used for wiimote to reset view - AcONWIIMOTEBUTTONRELEASED = 4005, // stops the surface interaction AcCHECKPOSITION = 5000, AcINITIALIZECONTOUR = 5001, AcCALCULATENEWSEGMENTATION_SP = 5002, AcINTERACTOR = 5003, AcCALCULATENEWSEGMENTATION_BB = 5004 }; /* //!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!! //EventMechanism: //If you change anything from here on, then change in mitkEventMapper.cpp (Array of constants) as well. //!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!! */ // Type of an Event; enum EEventType { Type_None = 0, // invalid event Type_Timer = 1, // timer event Type_MouseButtonPress = 2, // mouse button pressed Type_MouseButtonRelease = 3, // mouse button released Type_MouseButtonDblClick = 4, // mouse button double click Type_MouseMove = 5, // mouse move Type_KeyPress = 6, // key pressed Type_KeyRelease = 7, // key released Type_FocusIn = 8, // keyboard focus received Type_FocusOut = 9, // keyboard focus lost Type_Enter = 10, // mouse enters widget Type_Leave = 11, // mouse leaves widget Type_Paint = 12, // paint widget Type_Move = 13, // move widget Type_Resize = 14, // resize widget Type_Create = 15, // after object creation Type_Destroy = 16, // during object destruction Type_Show = 17, // widget is shown Type_Hide = 18, // widget is hidden Type_Close = 19, // request to close widget Type_Quit = 20, // request to quit application Type_Reparent = 21, // widget has been reparented Type_ShowMinimized = 22, // widget is shown minimized Type_ShowNormal = 23, // widget is shown normal Type_WindowActivate = 24, // window was activated Type_WindowDeactivate = 25, // window was deactivated Type_ShowToParent = 26, // widget is shown to parent Type_HideToParent = 27, // widget is hidden to parent Type_ShowMaximized = 28, // widget is shown maximized Type_ShowFullScreen = 29, // widget is shown full-screen Type_Accel = 30, // accelerator event Type_Wheel = 31, // wheel event Type_AccelAvailable = 32, // accelerator available event Type_CaptionChange = 33, // caption changed Type_IconChange = 34, // icon changed Type_ParentFontChange = 35, // parent font changed Type_ApplicationFontChange = 36, // application font changed Type_ParentPaletteChange = 37, // parent palette changed Type_ApplicationPaletteChange = 38, // application palette changed Type_PaletteChange = 39, // widget palette changed Type_Clipboard = 40, // internal clipboard event Type_Speech = 42, // reserved for speech input Type_SockAct = 50, // socket activation Type_AccelOverride = 51, // accelerator override event Type_DeferredDelete = 52, // deferred delete event Type_DragEnter = 60, // drag moves into widget Type_DragMove = 61, // drag moves in widget Type_DragLeave = 62, // drag leaves or is cancelled Type_Drop = 63, // actual drop Type_DragResponse = 64, // drag accepted/rejected Type_ChildInserted = 70, // new child widget Type_ChildRemoved = 71, // deleted child widget Type_LayoutHint = 72, // child min/max size changed Type_ShowWindowRequest = 73, // widget's window should be mapped Type_ActivateControl = 80, // ActiveX activation Type_DeactivateControl = 81, // ActiveX deactivation Type_ContextMenu = 82, // context popup menu Type_IMStart = 83, // input method composition start Type_IMCompose = 84, // input method composition Type_IMEnd = 85, // input method composition end Type_Accessibility = 86, // accessibility information is requested Type_TabletMove = 87, // Wacom tablet event Type_LocaleChange = 88, // the system locale changed Type_LanguageChange = 89, // the application language changed Type_LayoutDirectionChange = 90, // the layout direction changed Type_Style = 91, // internal style event Type_TabletPress = 92, // tablet press Type_TabletRelease = 93, // tablet release Type_User = 1000, // first user event id - Type_SpaceNavigatorInput = 1094, // 3D mouse input occurred - Type_SpaceNavigatorKeyDown = 1095, // 3D mouse input occurred - Type_WiiMoteInput = 1096, // WiiMote input occurred - Type_WiiMoteButton = 1097, // WiiMote button pressed Type_MaxUser = 65535 }; //##ButtonState // mouse/keyboard state values // QT combinations if MOUSEBUTTONRelease: left MouseButton + ControlButton: 0x201 enum EButtonStates { BS_NoButton = 0x0000, BS_LeftButton = 0x0001, BS_RightButton = 0x0002, BS_MidButton = 0x0004, BS_MouseButtonMask = 0x0007, BS_ShiftButton = 0x0100, BS_ControlButton = 0x0200, BS_AltButton = 0x0400, BS_MetaButton = 0x0800, BS_KeyButtonMask = 0x0f00, BS_Keypad = 0x4000 }; //##Key enum EKeys { Key_Escape = 0x1000, // misc keys Key_Tab = 0x1001, Key_Backtab = 0x1002, Key_BackTab = 0x1002, //= Key_Backtab Key_Backspace = 0x1003, Key_BackSpace = 0x1003, //= Key_Backspace Key_Return = 0x1004, Key_Enter = 0x1005, Key_Insert = 0x1006, Key_Delete = 0x1007, Key_Pause = 0x1008, Key_Print = 0x1009, Key_SysReq = 0x100a, Key_Home = 0x1010, // cursor movement Key_End = 0x1011, Key_Left = 0x1012, Key_Up = 0x1013, Key_Right = 0x1014, Key_Down = 0x1015, Key_Prior = 0x1016, Key_PageUp = 0x1016, //=Key_Prior Key_Next = 0x1017, Key_PageDown = 0x1017, //=Key_Next Key_Shift = 0x1020, // modifiers Key_Control = 0x1021, Key_Meta = 0x1022, Key_Alt = 0x1023, Key_CapsLock = 0x1024, Key_NumLock = 0x1025, Key_ScrollLock = 0x1026, Key_F1 = 0x1030, // function keys Key_F2 = 0x1031, Key_F3 = 0x1032, Key_F4 = 0x1033, Key_F5 = 0x1034, Key_F6 = 0x1035, Key_F7 = 0x1036, Key_F8 = 0x1037, Key_F9 = 0x1038, Key_F10 = 0x1039, Key_F11 = 0x103a, Key_F12 = 0x103b, Key_F13 = 0x103c, Key_F14 = 0x103d, Key_F15 = 0x103e, Key_F16 = 0x103f, Key_F17 = 0x1040, Key_F18 = 0x1041, Key_F19 = 0x1042, Key_F20 = 0x1043, Key_F21 = 0x1044, Key_F22 = 0x1045, Key_F23 = 0x1046, Key_F24 = 0x1047, Key_F25 = 0x1048, // F25 .. F35 only on X11 Key_F26 = 0x1049, Key_F27 = 0x104a, Key_F28 = 0x104b, Key_F29 = 0x104c, Key_F30 = 0x104d, Key_F31 = 0x104e, Key_F32 = 0x104f, Key_F33 = 0x1050, Key_F34 = 0x1051, Key_F35 = 0x1052, Key_Super_L = 0x1053, // extra keys Key_Super_R = 0x1054, Key_Menu = 0x1055, Key_Hyper_L = 0x1056, Key_Hyper_R = 0x1057, Key_Help = 0x1058, // International input method support (X keycode - = 0xEE00) // Only interesting if you are writing your own input method Key_Muhenkan = 0x1122, // Cancel Conversion Key_Henkan = 0x1123, // Start/Stop Conversion Key_Hiragana_Katakana = 0x1127, // Hiragana/Katakana toggle Key_Zenkaku_Hankaku = 0x112A, // Zenkaku/Hankaku toggle Key_Space = 0x20, // 7 bit printable ASCII Key_Any = 0x20, //= Key_Space Key_Exclam = 0x21, Key_QuoteDbl = 0x22, Key_NumberSign = 0x23, Key_Dollar = 0x24, Key_Percent = 0x25, Key_Ampersand = 0x26, Key_Apostrophe = 0x27, Key_ParenLeft = 0x28, Key_ParenRight = 0x29, Key_Asterisk = 0x2a, Key_Plus = 0x2b, Key_Comma = 0x2c, Key_Minus = 0x2d, Key_Period = 0x2e, Key_Slash = 0x2f, Key_0 = 0x30, Key_1 = 0x31, Key_2 = 0x32, Key_3 = 0x33, Key_4 = 0x34, Key_5 = 0x35, Key_6 = 0x36, Key_7 = 0x37, Key_8 = 0x38, Key_9 = 0x39, Key_Colon = 0x3a, Key_Semicolon = 0x3b, Key_Less = 0x3c, Key_Equal = 0x3d, Key_Greater = 0x3e, Key_Question = 0x3f, Key_At = 0x40, Key_A = 0x41, Key_B = 0x42, Key_C = 0x43, Key_D = 0x44, Key_E = 0x45, Key_F = 0x46, Key_G = 0x47, Key_H = 0x48, Key_I = 0x49, Key_J = 0x4a, Key_K = 0x4b, Key_L = 0x4c, Key_M = 0x4d, Key_N = 0x4e, Key_O = 0x4f, Key_P = 0x50, Key_Q = 0x51, Key_R = 0x52, Key_S = 0x53, Key_T = 0x54, Key_U = 0x55, Key_V = 0x56, Key_W = 0x57, Key_X = 0x58, Key_Y = 0x59, Key_Z = 0x5a, Key_BracketLeft = 0x5b, Key_Backslash = 0x5c, Key_BracketRight = 0x5d, Key_AsciiCircum = 0x5e, Key_Underscore = 0x5f, Key_QuoteLeft = 0x60, Key_BraceLeft = 0x7b, Key_Bar = 0x7c, Key_BraceRight = 0x7d, Key_AsciiTilde = 0x7e, Key_nobreakspace = 0x0a0, Key_exclamdown = 0x0a1, Key_cent = 0x0a2, Key_sterling = 0x0a3, Key_currency = 0x0a4, Key_yen = 0x0a5, Key_brokenbar = 0x0a6, Key_section = 0x0a7, Key_diaeresis = 0x0a8, Key_copyright = 0x0a9, Key_ordfeminine = 0x0aa, Key_guillemotleft = 0x0ab, // left angle quotation mark Key_notsign = 0x0ac, Key_hyphen = 0x0ad, Key_registered = 0x0ae, Key_macron = 0x0af, Key_degree = 0x0b0, Key_plusminus = 0x0b1, Key_twosuperior = 0x0b2, Key_threesuperior = 0x0b3, Key_acute = 0x0b4, Key_mu = 0x0b5, Key_paragraph = 0x0b6, Key_periodcentered = 0x0b7, Key_cedilla = 0x0b8, Key_onesuperior = 0x0b9, Key_masculine = 0x0ba, Key_guillemotright = 0x0bb, // right angle quotation mark Key_onequarter = 0x0bc, Key_onehalf = 0x0bd, Key_threequarters = 0x0be, Key_questiondown = 0x0bf, Key_Agrave = 0x0c0, Key_Aacute = 0x0c1, Key_Acircumflex = 0x0c2, Key_Atilde = 0x0c3, Key_Adiaeresis = 0x0c4, Key_Aring = 0x0c5, Key_AE = 0x0c6, Key_Ccedilla = 0x0c7, Key_Egrave = 0x0c8, Key_Eacute = 0x0c9, Key_Ecircumflex = 0x0ca, Key_Ediaeresis = 0x0cb, Key_Igrave = 0x0cc, Key_Iacute = 0x0cd, Key_Icircumflex = 0x0ce, Key_Idiaeresis = 0x0cf, Key_ETH = 0x0d0, Key_Ntilde = 0x0d1, Key_Ograve = 0x0d2, Key_Oacute = 0x0d3, Key_Ocircumflex = 0x0d4, Key_Otilde = 0x0d5, Key_Odiaeresis = 0x0d6, Key_multiply = 0x0d7, Key_Ooblique = 0x0d8, Key_Ugrave = 0x0d9, Key_Uacute = 0x0da, Key_Ucircumflex = 0x0db, Key_Udiaeresis = 0x0dc, Key_Yacute = 0x0dd, Key_THORN = 0x0de, Key_ssharp = 0x0df, Key_agrave = 0x0e0, Key_aacute = 0x0e1, Key_acircumflex = 0x0e2, Key_atilde = 0x0e3, Key_adiaeresis = 0x0e4, Key_aring = 0x0e5, Key_ae = 0x0e6, Key_ccedilla = 0x0e7, Key_egrave = 0x0e8, Key_eacute = 0x0e9, Key_ecircumflex = 0x0ea, Key_ediaeresis = 0x0eb, Key_igrave = 0x0ec, Key_iacute = 0x0ed, Key_icircumflex = 0x0ee, Key_idiaeresis = 0x0ef, Key_eth = 0x0f0, Key_ntilde = 0x0f1, Key_ograve = 0x0f2, Key_oacute = 0x0f3, Key_ocircumflex = 0x0f4, Key_otilde = 0x0f5, Key_odiaeresis = 0x0f6, Key_division = 0x0f7, Key_oslash = 0x0f8, Key_ugrave = 0x0f9, Key_uacute = 0x0fa, Key_ucircumflex = 0x0fb, Key_udiaeresis = 0x0fc, Key_yacute = 0x0fd, Key_thorn = 0x0fe, Key_ydiaeresis = 0x0ff, Key_unknown = 0xffff, Key_none = 0xffff //= Key_unknown }; } // namespace mitk #endif diff --git a/Modules/Core/src/Rendering/mitkPointSetVtkMapper3D.cpp b/Modules/Core/src/Rendering/mitkPointSetVtkMapper3D.cpp index 8c30df1c50..beafa5cc99 100644 --- a/Modules/Core/src/Rendering/mitkPointSetVtkMapper3D.cpp +++ b/Modules/Core/src/Rendering/mitkPointSetVtkMapper3D.cpp @@ -1,614 +1,594 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPointSetVtkMapper3D.h" #include "mitkColorProperty.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkProperties.h" #include "mitkVtkPropRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const mitk::PointSet *mitk::PointSetVtkMapper3D::GetInput() { return static_cast(GetDataNode()->GetData()); } mitk::PointSetVtkMapper3D::PointSetVtkMapper3D() : m_vtkSelectedPointList(nullptr), m_vtkUnselectedPointList(nullptr), m_VtkSelectedPolyDataMapper(nullptr), m_VtkUnselectedPolyDataMapper(nullptr), m_vtkTextList(nullptr), m_NumberOfSelectedAdded(0), m_NumberOfUnselectedAdded(0), m_PointSize(1.0), m_ContourRadius(0.5) { // propassembly m_PointsAssembly = vtkSmartPointer::New(); // creating actors to be able to set transform m_SelectedActor = vtkSmartPointer::New(); m_UnselectedActor = vtkSmartPointer::New(); m_ContourActor = vtkSmartPointer::New(); } mitk::PointSetVtkMapper3D::~PointSetVtkMapper3D() { } void mitk::PointSetVtkMapper3D::ReleaseGraphicsResources(vtkWindow *renWin) { m_PointsAssembly->ReleaseGraphicsResources(renWin); m_SelectedActor->ReleaseGraphicsResources(renWin); m_UnselectedActor->ReleaseGraphicsResources(renWin); m_ContourActor->ReleaseGraphicsResources(renWin); } void mitk::PointSetVtkMapper3D::ReleaseGraphicsResources(mitk::BaseRenderer *renderer) { m_PointsAssembly->ReleaseGraphicsResources(renderer->GetRenderWindow()); m_SelectedActor->ReleaseGraphicsResources(renderer->GetRenderWindow()); m_UnselectedActor->ReleaseGraphicsResources(renderer->GetRenderWindow()); m_ContourActor->ReleaseGraphicsResources(renderer->GetRenderWindow()); } void mitk::PointSetVtkMapper3D::CreateVTKRenderObjects() { m_vtkSelectedPointList = vtkSmartPointer::New(); m_vtkUnselectedPointList = vtkSmartPointer::New(); m_PointsAssembly->VisibilityOn(); if (m_PointsAssembly->GetParts()->IsItemPresent(m_SelectedActor)) m_PointsAssembly->RemovePart(m_SelectedActor); if (m_PointsAssembly->GetParts()->IsItemPresent(m_UnselectedActor)) m_PointsAssembly->RemovePart(m_UnselectedActor); if (m_PointsAssembly->GetParts()->IsItemPresent(m_ContourActor)) m_PointsAssembly->RemovePart(m_ContourActor); - // exceptional displaying for PositionTracker -> MouseOrientationTool - int mapperID; - bool isInputDevice = false; - if (this->GetDataNode()->GetBoolProperty("inputdevice", isInputDevice) && isInputDevice) - { - if (this->GetDataNode()->GetIntProperty("BaseRendererMapperID", mapperID) && mapperID == BaseRenderer::Standard3D) - return; // The event for the PositionTracker came from the 3d widget and not needs to be displayed - } - // get and update the PointSet mitk::PointSet::Pointer input = const_cast(this->GetInput()); /* only update the input data, if the property tells us to */ bool update = true; this->GetDataNode()->GetBoolProperty("updateDataOnRender", update); if (update == true) input->Update(); int timestep = this->GetTimestep(); mitk::PointSet::DataType::Pointer itkPointSet = input->GetPointSet(timestep); if (itkPointSet.GetPointer() == nullptr) { m_PointsAssembly->VisibilityOff(); return; } // now fill selected and unselected pointList // get size of Points in Property m_PointSize = 2; mitk::FloatProperty::Pointer pointSizeProp = dynamic_cast(this->GetDataNode()->GetProperty("pointsize")); if (pointSizeProp.IsNotNull()) m_PointSize = pointSizeProp->GetValue(); // get the property for creating a label onto every point only once bool showLabel = true; this->GetDataNode()->GetBoolProperty("show label", showLabel); const char *pointLabel = nullptr; if (showLabel) { if (dynamic_cast(this->GetDataNode()->GetPropertyList()->GetProperty("label")) != nullptr) pointLabel = dynamic_cast(this->GetDataNode()->GetPropertyList()->GetProperty("label"))->GetValue(); else showLabel = false; } // whether or not to creat a "contour" - connecting lines between all the points int nbPoints = itkPointSet->GetPointData()->Size(); bool makeContour = false; this->GetDataNode()->GetBoolProperty("show contour", makeContour); bool closeContour = false; this->GetDataNode()->GetBoolProperty("close contour", closeContour); int contourPointLimit = 0; // NO contour if (makeContour) { if (closeContour) contourPointLimit = nbPoints; else contourPointLimit = nbPoints - 1; } // build list of all positions for later transform in one go mitk::PointSet::PointsContainer::Iterator pointsIter; int ptIdx; m_NumberOfSelectedAdded = 0; m_NumberOfUnselectedAdded = 0; vtkSmartPointer localPoints = vtkSmartPointer::New(); m_WorldPositions = vtkSmartPointer::New(); m_PointConnections = vtkSmartPointer::New(); // m_PointConnections between points for (ptIdx = 0, pointsIter = itkPointSet->GetPoints()->Begin(); pointsIter != itkPointSet->GetPoints()->End(); pointsIter++, ptIdx++) { itk::Point currentPoint = pointsIter->Value(); localPoints->InsertPoint(ptIdx, currentPoint[0], currentPoint[1], currentPoint[2]); if (makeContour && ptIdx < contourPointLimit) { vtkIdType cell[2] = {(ptIdx + 1) % nbPoints, ptIdx}; m_PointConnections->InsertNextCell(2, cell); } } vtkSmartPointer vtktransform = this->GetDataNode()->GetVtkTransform(this->GetTimestep()); vtktransform->TransformPoints(localPoints, m_WorldPositions); // create contour if (makeContour) { this->CreateContour(m_WorldPositions, m_PointConnections); } // check if the list for the PointDataContainer is the same size as the PointsContainer. Is not, then the points were // inserted manually and can not be visualized according to the PointData (selected/unselected) bool pointDataBroken = (itkPointSet->GetPointData()->Size() != itkPointSet->GetPoints()->Size()); // now add an object for each point in data mitk::PointSet::PointDataContainer::Iterator pointDataIter = itkPointSet->GetPointData()->Begin(); for (ptIdx = 0; ptIdx < nbPoints; ++ptIdx) // pointDataIter moved at end of loop { double currentPoint[3]; m_WorldPositions->GetPoint(ptIdx, currentPoint); vtkSmartPointer source; // check for the pointtype in data and decide which geom-object to take and then add to the selected or unselected // list int pointType; if (itkPointSet->GetPointData()->size() == 0 || pointDataBroken) pointType = mitk::PTUNDEFINED; else pointType = pointDataIter.Value().pointSpec; switch (pointType) { case mitk::PTUNDEFINED: { vtkSmartPointer sphere = vtkSmartPointer::New(); sphere->SetRadius(m_PointSize / 2.0f); sphere->SetCenter(currentPoint); - // sphere->SetCenter(pointsIter.Value()[0],pointsIter.Value()[1],pointsIter.Value()[2]); - - // MouseOrientation Tool (PositionTracker) - if (isInputDevice) - { - sphere->SetThetaResolution(10); - sphere->SetPhiResolution(10); - } - else - { - sphere->SetThetaResolution(20); - sphere->SetPhiResolution(20); - } + sphere->SetThetaResolution(20); + sphere->SetPhiResolution(20); source = sphere; } break; case mitk::PTSTART: { vtkSmartPointer cube = vtkSmartPointer::New(); cube->SetXLength(m_PointSize / 2); cube->SetYLength(m_PointSize / 2); cube->SetZLength(m_PointSize / 2); cube->SetCenter(currentPoint); source = cube; } break; case mitk::PTCORNER: { vtkSmartPointer cone = vtkSmartPointer::New(); cone->SetRadius(m_PointSize / 2.0f); cone->SetCenter(currentPoint); cone->SetResolution(20); source = cone; } break; case mitk::PTEDGE: { vtkSmartPointer cylinder = vtkSmartPointer::New(); cylinder->SetRadius(m_PointSize / 2.0f); cylinder->SetCenter(currentPoint); cylinder->SetResolution(20); source = cylinder; } break; case mitk::PTEND: { vtkSmartPointer sphere = vtkSmartPointer::New(); sphere->SetRadius(m_PointSize / 2.0f); // no SetCenter?? this functionality should be explained! // otherwise: join with default block! sphere->SetThetaResolution(20); sphere->SetPhiResolution(20); source = sphere; } break; default: { vtkSmartPointer sphere = vtkSmartPointer::New(); sphere->SetRadius(m_PointSize / 2.0f); sphere->SetCenter(currentPoint); sphere->SetThetaResolution(20); sphere->SetPhiResolution(20); source = sphere; } break; } if (pointDataIter.Value().selected && !pointDataBroken) { m_vtkSelectedPointList->AddInputConnection(source->GetOutputPort()); ++m_NumberOfSelectedAdded; } else { m_vtkUnselectedPointList->AddInputConnection(source->GetOutputPort()); ++m_NumberOfUnselectedAdded; } if (showLabel) { char buffer[20]; std::string l = pointLabel; if (input->GetSize() > 1) { sprintf(buffer, "%d", ptIdx + 1); l.append(buffer); } // Define the text for the label vtkSmartPointer label = vtkSmartPointer::New(); label->SetText(l.c_str()); //# Set up a transform to move the label to a new position. vtkSmartPointer aLabelTransform = vtkSmartPointer::New(); aLabelTransform->Identity(); aLabelTransform->Translate(currentPoint[0] + 2, currentPoint[1] + 2, currentPoint[2]); aLabelTransform->Scale(5.7, 5.7, 5.7); //# Move the label to a new position. vtkSmartPointer labelTransform = vtkSmartPointer::New(); labelTransform->SetTransform(aLabelTransform); labelTransform->SetInputConnection(label->GetOutputPort()); // add it to the wright PointList if (pointType) { m_vtkSelectedPointList->AddInputConnection(labelTransform->GetOutputPort()); ++m_NumberOfSelectedAdded; } else { m_vtkUnselectedPointList->AddInputConnection(labelTransform->GetOutputPort()); ++m_NumberOfUnselectedAdded; } } if (pointDataIter != itkPointSet->GetPointData()->End()) pointDataIter++; } // end FOR // now according to number of elements added to selected or unselected, build up the rendering pipeline if (m_NumberOfSelectedAdded > 0) { m_VtkSelectedPolyDataMapper = vtkSmartPointer::New(); m_VtkSelectedPolyDataMapper->SetInputConnection(m_vtkSelectedPointList->GetOutputPort()); // create a new instance of the actor m_SelectedActor = vtkSmartPointer::New(); m_SelectedActor->SetMapper(m_VtkSelectedPolyDataMapper); m_PointsAssembly->AddPart(m_SelectedActor); } if (m_NumberOfUnselectedAdded > 0) { m_VtkUnselectedPolyDataMapper = vtkSmartPointer::New(); m_VtkUnselectedPolyDataMapper->SetInputConnection(m_vtkUnselectedPointList->GetOutputPort()); // create a new instance of the actor m_UnselectedActor = vtkSmartPointer::New(); m_UnselectedActor->SetMapper(m_VtkUnselectedPolyDataMapper); m_PointsAssembly->AddPart(m_UnselectedActor); } } void mitk::PointSetVtkMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { m_UnselectedActor->VisibilityOff(); m_SelectedActor->VisibilityOff(); m_ContourActor->VisibilityOff(); return; } // create new vtk render objects (e.g. sphere for a point) BaseLocalStorage *ls = m_LSH.GetLocalStorage(renderer); bool needGenerateData = ls->IsGenerateDataRequired(renderer, this, GetDataNode()); if (!needGenerateData) { if (this->GetDataNode()->GetPropertyList()->GetMTime() > ls->GetLastGenerateDataTime() || this->GetDataNode()->GetPropertyList(renderer)->GetMTime() > ls->GetLastGenerateDataTime()) { needGenerateData = true; } } if (needGenerateData) { this->CreateVTKRenderObjects(); ls->UpdateGenerateDataTime(); } this->ApplyAllProperties(renderer, m_ContourActor); bool showPoints = true; this->GetDataNode()->GetBoolProperty("show points", showPoints); m_UnselectedActor->SetVisibility(showPoints); m_SelectedActor->SetVisibility(showPoints); if (false && dynamic_cast(this->GetDataNode()->GetProperty("opacity")) != nullptr) { mitk::FloatProperty::Pointer pointOpacity = dynamic_cast(this->GetDataNode()->GetProperty("opacity")); float opacity = pointOpacity->GetValue(); m_ContourActor->GetProperty()->SetOpacity(opacity); m_UnselectedActor->GetProperty()->SetOpacity(opacity); m_SelectedActor->GetProperty()->SetOpacity(opacity); } bool showContour = false; this->GetDataNode()->GetBoolProperty("show contour", showContour); m_ContourActor->SetVisibility(showContour); } void mitk::PointSetVtkMapper3D::ResetMapper(BaseRenderer * /*renderer*/) { m_PointsAssembly->VisibilityOff(); } vtkProp *mitk::PointSetVtkMapper3D::GetVtkProp(mitk::BaseRenderer * /*renderer*/) { return m_PointsAssembly; } void mitk::PointSetVtkMapper3D::UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) { } void mitk::PointSetVtkMapper3D::ApplyAllProperties(mitk::BaseRenderer *renderer, vtkActor *actor) { Superclass::ApplyColorAndOpacityProperties(renderer, actor); // check for color props and use it for rendering of selected/unselected points and contour // due to different params in VTK (double/float) we have to convert! // vars to convert to double unselectedColor[4] = {1.0f, 1.0f, 0.0f, 1.0f}; // yellow double selectedColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // red double contourColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // red // different types for color!!! mitk::Color tmpColor; double opacity = 1.0; // check if there is an unselected property if (dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("unselectedcolor")) != nullptr) { tmpColor = dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("unselectedcolor")) ->GetValue(); unselectedColor[0] = tmpColor[0]; unselectedColor[1] = tmpColor[1]; unselectedColor[2] = tmpColor[2]; unselectedColor[3] = 1.0f; //!!define a new ColorProp to be able to pass alpha value } else if (dynamic_cast( this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("unselectedcolor")) != nullptr) { tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("unselectedcolor")) ->GetValue(); unselectedColor[0] = tmpColor[0]; unselectedColor[1] = tmpColor[1]; unselectedColor[2] = tmpColor[2]; unselectedColor[3] = 1.0f; //!!define a new ColorProp to be able to pass alpha value } else { // check if the node has a color float unselectedColorTMP[4] = {1.0f, 1.0f, 0.0f, 1.0f}; // yellow m_DataNode->GetColor(unselectedColorTMP, nullptr); unselectedColor[0] = unselectedColorTMP[0]; unselectedColor[1] = unselectedColorTMP[1]; unselectedColor[2] = unselectedColorTMP[2]; // unselectedColor[3] stays 1.0f } // get selected property if (dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("selectedcolor")) != nullptr) { tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(renderer)->GetProperty("selectedcolor")) ->GetValue(); selectedColor[0] = tmpColor[0]; selectedColor[1] = tmpColor[1]; selectedColor[2] = tmpColor[2]; selectedColor[3] = 1.0f; } else if (dynamic_cast( this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("selectedcolor")) != nullptr) { tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("selectedcolor")) ->GetValue(); selectedColor[0] = tmpColor[0]; selectedColor[1] = tmpColor[1]; selectedColor[2] = tmpColor[2]; selectedColor[3] = 1.0f; } // get contour property if (dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("contourcolor")) != nullptr) { tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(renderer)->GetProperty("contourcolor")) ->GetValue(); contourColor[0] = tmpColor[0]; contourColor[1] = tmpColor[1]; contourColor[2] = tmpColor[2]; contourColor[3] = 1.0f; } else if (dynamic_cast( this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("contourcolor")) != nullptr) { tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("contourcolor")) ->GetValue(); contourColor[0] = tmpColor[0]; contourColor[1] = tmpColor[1]; contourColor[2] = tmpColor[2]; contourColor[3] = 1.0f; } if (dynamic_cast(this->GetDataNode()->GetPropertyList(renderer)->GetProperty("opacity")) != nullptr) { mitk::FloatProperty::Pointer pointOpacity = dynamic_cast(this->GetDataNode()->GetPropertyList(renderer)->GetProperty("opacity")); opacity = pointOpacity->GetValue(); } else if (dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("opacity")) != nullptr) { mitk::FloatProperty::Pointer pointOpacity = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("opacity")); opacity = pointOpacity->GetValue(); } // finished color / opacity fishing! // check if a contour shall be drawn bool showContour = false; this->GetDataNode()->GetBoolProperty("show contour", showContour, renderer); if (showContour && (m_ContourActor != nullptr)) { this->CreateContour(m_WorldPositions, m_PointConnections); m_ContourActor->GetProperty()->SetColor(contourColor); m_ContourActor->GetProperty()->SetOpacity(opacity); } m_SelectedActor->GetProperty()->SetColor(selectedColor); m_SelectedActor->GetProperty()->SetOpacity(opacity); m_UnselectedActor->GetProperty()->SetColor(unselectedColor); m_UnselectedActor->GetProperty()->SetOpacity(opacity); } void mitk::PointSetVtkMapper3D::CreateContour(vtkPoints *points, vtkCellArray *m_PointConnections) { vtkSmartPointer vtkContourPolyData = vtkSmartPointer::New(); vtkSmartPointer vtkContourPolyDataMapper = vtkSmartPointer::New(); vtkSmartPointer contour = vtkSmartPointer::New(); contour->SetPoints(points); contour->SetLines(m_PointConnections); vtkSmartPointer tubeFilter = vtkSmartPointer::New(); tubeFilter->SetNumberOfSides(12); tubeFilter->SetInputData(contour); // check for property contoursize. m_ContourRadius = 0.5; mitk::FloatProperty::Pointer contourSizeProp = dynamic_cast(this->GetDataNode()->GetProperty("contoursize")); if (contourSizeProp.IsNotNull()) m_ContourRadius = contourSizeProp->GetValue(); tubeFilter->SetRadius(m_ContourRadius); tubeFilter->Update(); // add to pipeline vtkContourPolyData->AddInputConnection(tubeFilter->GetOutputPort()); vtkContourPolyDataMapper->SetInputConnection(vtkContourPolyData->GetOutputPort()); m_ContourActor->SetMapper(vtkContourPolyDataMapper); m_PointsAssembly->AddPart(m_ContourActor); } void mitk::PointSetVtkMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("line width", mitk::IntProperty::New(2), renderer, overwrite); node->AddProperty("pointsize", mitk::FloatProperty::New(1.0), renderer, overwrite); node->AddProperty("selectedcolor", mitk::ColorProperty::New(1.0f, 0.0f, 0.0f), renderer, overwrite); // red node->AddProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 0.0f), renderer, overwrite); // yellow node->AddProperty("opacity", mitk::FloatProperty::New(1.0f), renderer, overwrite); node->AddProperty("show contour", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("close contour", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("contourcolor", mitk::ColorProperty::New(1.0f, 0.0f, 0.0f), renderer, overwrite); node->AddProperty("contoursize", mitk::FloatProperty::New(0.5), renderer, overwrite); node->AddProperty("show points", mitk::BoolProperty::New(true), renderer, overwrite); node->AddProperty("updateDataOnRender", mitk::BoolProperty::New(true), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp index 117946b90f..064c0efd5a 100644 --- a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp +++ b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp @@ -1,391 +1,409 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::string mitk::VideoRecorder::GetFileExtension(OutputFormat format) { switch (format) { case OutputFormat::WebM_VP9: return ".webm"; case OutputFormat::MP4_H264: return ".mp4"; default: break; } mitkThrow() << "Unknown output format for video recording."; } namespace { mitk::IPreferences* GetPreferences() { auto* preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.moviemaker"); } class RecordingSession { public: RecordingSession(vtkRenderWindow* renderWindow, mitk::VideoRecorder::OutputFormat format) : m_FrameDir(mitk::IOUtil::CreateTemporaryDirectory("MITK_RecordingSession_XXXXXX")), m_NumberOfFrames(0) { m_WindowToImageFilter->SetInput(renderWindow); if (mitk::VideoRecorder::OutputFormat::MP4_H264 == format) { // H.264 only supports image dimensions that are a multiple of 2. Resize if necessary. auto* size = renderWindow->GetActualSize(); if (size[0] & 1 || size[1] & 1) { m_ImageResize->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); m_ImageResize->SetOutputDimensions(size[0] & ~1, size[1] & ~1, -1); m_ImageResize->SetInterpolate(0); m_ImageResize->BorderOn(); m_ImageWriter->SetInputConnection(m_ImageResize->GetOutputPort()); return; } } m_ImageWriter->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); } ~RecordingSession() { std::error_code errorCode; std::filesystem::remove_all(m_FrameDir, errorCode); } RecordingSession(const RecordingSession&) = delete; RecordingSession& operator=(const RecordingSession&) = delete; std::filesystem::path GetFrameDir() const { return m_FrameDir; } void RecordFrame() { m_WindowToImageFilter->Modified(); std::stringstream frameFilename; frameFilename << std::setw(6) << std::setfill('0') << m_NumberOfFrames << ".png"; const auto framePath = m_FrameDir / frameFilename.str(); m_ImageWriter->SetFileName(framePath.string().c_str()); m_ImageWriter->Write(); ++m_NumberOfFrames; } private: std::filesystem::path m_FrameDir; unsigned int m_NumberOfFrames; vtkNew m_WindowToImageFilter; vtkNew m_ImageResize; vtkNew m_ImageWriter; }; } namespace mitk { class VideoRecorder::Impl { public: Impl() : m_FrameRate(30) { } ~Impl() = default; Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; std::filesystem::path GetFFmpegPath() const { if (m_FFmpegPath) return m_FFmpegPath.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) { auto ffmpegPath = preferences->Get("ffmpeg", ""); if (!ffmpegPath.empty()) return ffmpegPath; } return std::filesystem::path(); } void SetFFmpegPath(const std::filesystem::path& path) { m_FFmpegPath = path; } std::filesystem::path GetOutputPath() const { return m_OutputPath; } void SetOutputPath(const std::filesystem::path& path) { m_OutputPath = path; } mitk::VideoRecorder::OutputFormat GetOutputFormat() const { if (m_OutputFormat) return m_OutputFormat.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) return static_cast(preferences->GetInt("format", 0)); return OutputFormat::WebM_VP9; } void SetOutputFormat(OutputFormat format) { m_OutputFormat = format; } std::string GetRenderWindowName() const { return m_RenderWindowName; } void SetRenderWindowName(const std::string& renderWindowName) { m_RenderWindowName = renderWindowName; } int GetFrameRate() const { return m_FrameRate; } void SetFrameRate(unsigned int fps) { m_FrameRate = fps; } bool OnAir() const { return nullptr != m_RecordingSession.get(); } void StartRecording() { if (this->OnAir()) mitkThrow() << "Recording session already running."; auto renderWindowName = this->GetRenderWindowName(); if (renderWindowName.empty()) mitkThrow() << "No render window specified for recording."; auto* renderWindow = BaseRenderer::GetRenderWindowByName(renderWindowName); if (nullptr == renderWindow) mitkThrow() << "\"" << renderWindowName << "\" references unknown render window for recording."; m_RecordingSession = std::make_unique(renderWindow, this->GetOutputFormat()); } void RecordFrame() { if (!this->OnAir()) mitkThrow() << "Cannot record frame. No recording session running."; m_RecordingSession->RecordFrame(); } std::string GetFFmpegCommandLine() const { bool vp9 = OutputFormat::WebM_VP9 == this->GetOutputFormat(); std::stringstream stream; - stream << this->GetFFmpegPath() + stream << this->GetFFmpegPath() << ' ' << "-y" << ' ' << "-r " << std::to_string(this->GetFrameRate()) << ' ' << "-i %6d.png" << ' ' << "-c:v " << (vp9 ? "libvpx-vp9" : "libx264") << ' ' << "-crf " << (vp9 ? "31" : "23") << ' ' << "-pix_fmt yuv420p" << ' ' << "-b:v 0" << ' ' << this->GetOutputPath(); return stream.str(); } int ExecuteFFmpeg() const { auto commandLine = this->GetFFmpegCommandLine(); auto commandLineCStr = commandLine.c_str(); auto workingDirectory = m_RecordingSession->GetFrameDir().string(); auto* ffmpeg = itksysProcess_New(); itksysProcess_SetOption(ffmpeg, itksysProcess_Option_Verbatim, 1); itksysProcess_SetCommand(ffmpeg, &commandLineCStr); itksysProcess_SetWorkingDirectory(ffmpeg, workingDirectory.c_str()); itksysProcess_Execute(ffmpeg); itksysProcess_WaitForExit(ffmpeg, nullptr); - if (itksysProcess_State_Exited != itksysProcess_GetState(ffmpeg)) + auto state = itksysProcess_GetState(ffmpeg); + + if (itksysProcess_State_Exited != state) { + std::stringstream message; + message << "FFmpeg process did not exit as expected: "; + + if (itksysProcess_State_Error == state) + { + message << itksysProcess_GetErrorString(ffmpeg); + } + else if (itksysProcess_State_Exception == state) + { + message << itksysProcess_GetExceptionString(ffmpeg); + } + + message << "\n Command: " << commandLineCStr; + message << "\n Working directory: " << workingDirectory.c_str(); + itksysProcess_Delete(ffmpeg); - mitkThrow() << "FFmpeg process did not exit as expected."; + + mitkThrow() << message.str(); } auto exitCode = itksysProcess_GetExitValue(ffmpeg); itksysProcess_Delete(ffmpeg); return exitCode; } int StopRecording() { if (!this->OnAir()) mitkThrow() << "No recording session running."; if (this->GetFFmpegPath().empty()) mitkThrow() << "Path to FFmpeg not set."; if (this->GetOutputPath().empty()) mitkThrow() << "Path to output video file not set."; auto exitCode = this->ExecuteFFmpeg(); m_RecordingSession = nullptr; return exitCode; } private: std::optional m_FFmpegPath; std::filesystem::path m_OutputPath; std::optional m_OutputFormat; std::string m_RenderWindowName; unsigned int m_FrameRate; std::unique_ptr m_RecordingSession; }; } mitk::VideoRecorder::VideoRecorder() : m_Impl(std::make_unique()) { } mitk::VideoRecorder::~VideoRecorder() { } std::filesystem::path mitk::VideoRecorder::GetFFmpegPath() const { return m_Impl->GetFFmpegPath(); } void mitk::VideoRecorder::SetFFmpegPath(const std::filesystem::path& path) { m_Impl->SetFFmpegPath(path); } std::filesystem::path mitk::VideoRecorder::GetOutputPath() const { return m_Impl->GetOutputPath(); } void mitk::VideoRecorder::SetOutputPath(const std::filesystem::path& path) { m_Impl->SetOutputPath(path); } mitk::VideoRecorder::OutputFormat mitk::VideoRecorder::GetOutputFormat() const { return m_Impl->GetOutputFormat(); } void mitk::VideoRecorder::SetOutputFormat(OutputFormat format) { m_Impl->SetOutputFormat(format); } std::string mitk::VideoRecorder::GetRenderWindowName() const { return m_Impl->GetRenderWindowName(); } void mitk::VideoRecorder::SetRenderWindowName(const std::string& renderWindowName) { m_Impl->SetRenderWindowName(renderWindowName); } int mitk::VideoRecorder::GetFrameRate() const { return m_Impl->GetFrameRate(); } void mitk::VideoRecorder::SetFrameRate(unsigned int fps) { m_Impl->SetFrameRate(fps); } void mitk::VideoRecorder::StartRecording() { m_Impl->StartRecording(); } void mitk::VideoRecorder::RecordFrame() const { m_Impl->RecordFrame(); } int mitk::VideoRecorder::StopRecording() { return m_Impl->StopRecording(); } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 5dabaf8adb..48cfd421c1 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1553 +1,1562 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } + this->ReinitMaps(); + // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledValue); } ls->SetLayer(newLabelSetId); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetActiveLayer() != groupID ? this->GetLayerImage(groupID) : this; if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) { this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 //bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const //{ // auto finding = m_LabelToGroupMap.find(value); // if (m_LabelToGroupMap.end() != finding) // { // return finding->second == groupIndex; // } // return false; //} // //bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const //{ // return index < m_LabelSetContainer.size(); //} bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LabelSetContainer.size(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UnlabeledValue) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) - { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; - } mitk::LabelSetImage::ConstLabelVectorType result; + const auto labelValues = m_GroupToLabelMap.find(index)->second; - const auto labellist = m_GroupToLabelMap.find(index)->second; - for (const auto& labelvalue : labellist) + for (const auto& labelValue : labelValues) { - result.emplace_back(this->GetLabel(labelvalue)); + const auto* label = this->GetLabel(labelValue); + + if (label != nullptr) + result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) { if (!this->ExistGroup(index)) - { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; - } mitk::LabelSetImage::LabelVectorType result; + const auto labelValues = m_GroupToLabelMap[index]; - const auto labellist = m_GroupToLabelMap[index]; - for (const auto& labelvalue : labellist) + for (const auto& labelValue : labelValues) { - result.emplace_back(this->GetLabel(labelvalue)); + auto* label = this->GetLabel(labelValue); + + if (label != nullptr) + result.emplace_back(label); } return result; } void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); - for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) + + if (labelSet->GetNumberOfLabels() != 0) { - if (iter->first != UnlabeledValue) + for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { - this->AddLabelToMap(iter->first, iter->second, layerID); + if (iter->first != UnlabeledValue) + this->AddLabelToMap(iter->first, iter->second, layerID); } } + else + { + m_GroupToLabelMap[layerID] = {}; + } } } - bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp index 0578c2e0d5..1f80c59170 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp @@ -1,134 +1,138 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiWidgetLayoutSelectionWidget.h" #include #include #include #include #include QmitkMultiWidgetLayoutSelectionWidget::QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent/* = 0*/) : QWidget(parent) { Init(); } void QmitkMultiWidgetLayoutSelectionWidget::Init() { ui.setupUi(this); auto stylesheet = "QTableWidget::item{background-color: white;}\nQTableWidget::item:selected{background-color: #1C97EA;}"; ui.tableWidget->setStyleSheet(stylesheet); connect(ui.tableWidget, &QTableWidget::itemSelectionChanged, this, &QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged); connect(ui.setLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked); connect(ui.loadLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked); connect(ui.saveLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked); connect(ui.selectDefaultLayoutComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected); ui.selectDefaultLayoutComboBox->addItem("Select a layout preset"); auto presetResources = us::GetModuleContext()->GetModule()->FindResources("/", "mxnLayout_*.json", false); for (const auto& resource : presetResources) { us::ModuleResourceStream jsonStream(resource); auto data = nlohmann::json::parse(jsonStream); auto resourceName = data["name"].get(); ui.selectDefaultLayoutComboBox->addItem(QString::fromStdString(resourceName)); m_PresetMap[ui.selectDefaultLayoutComboBox->count() - 1] = data; } } void QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged() { QItemSelectionModel* selectionModel = ui.tableWidget->selectionModel(); int row = 0; int column = 0; QModelIndexList indices = selectionModel->selectedIndexes(); if (indices.size() > 0) { row = indices[0].row(); column = indices[0].column(); QModelIndex topLeft = ui.tableWidget->model()->index(0, 0, QModelIndex()); QModelIndex bottomRight = ui.tableWidget->model()->index(row, column, QModelIndex()); QItemSelection cellSelection; cellSelection.select(topLeft, bottomRight); selectionModel->select(cellSelection, QItemSelectionModel::Select); } } void QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked() { int row = 0; int column = 0; QModelIndexList indices = ui.tableWidget->selectionModel()->selectedIndexes(); if (indices.size() > 0) { // find largest row and column for (const auto& modelIndex : qAsConst(indices)) { if (modelIndex.row() > row) { row = modelIndex.row(); } if (modelIndex.column() > column) { column = modelIndex.column(); } } close(); emit LayoutSet(row+1, column+1); } ui.selectDefaultLayoutComboBox->setCurrentIndex(0); } void QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked() { QString filename = QFileDialog::getSaveFileName(nullptr, "Select where to save the current layout", "", "MITK Window Layout (*.json)"); if (filename.isEmpty()) return; + QString fileExt(".json"); + if (!filename.endsWith(fileExt)) + filename += fileExt; + auto outStream = std::ofstream(filename.toStdString()); emit SaveLayout(&outStream); } void QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked() { QString filename = QFileDialog::getOpenFileName(nullptr, "Load a layout file", "", "MITK Window Layouts (*.json)"); if (filename.isEmpty()) return; ui.selectDefaultLayoutComboBox->setCurrentIndex(0); std::ifstream f(filename.toStdString()); auto jsonData = nlohmann::json::parse(f); emit LoadLayout(&jsonData); } void QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected(int index) { if (index == 0) { // First entry is only for description return; } auto jsonData = m_PresetMap[index]; close(); emit LoadLayout(&jsonData); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index baad32e9c2..665c7a1714 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,122 +1,125 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkBinaryThresholdBaseTool.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLabelSetImage.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThreshold(-100), m_SensibleMaximumThreshold(+100), m_LowerThreshold(1), m_UpperThreshold(1) { } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThreshold || lower > m_SensibleMaximumThreshold || upper < m_SensibleMinimumThreshold || upper > m_SensibleMaximumThreshold) { return; } m_LowerThreshold = lower; m_UpperThreshold = upper; if (nullptr != this->GetPreviewSegmentation()) { UpdatePreview(); } } void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { const auto referenceImage = this->GetReferenceData(); if (nullptr != referenceImage) { m_SensibleMinimumThreshold = std::numeric_limits::max(); m_SensibleMaximumThreshold = std::numeric_limits::lowest(); Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { m_SensibleMinimumThreshold = std::min(m_SensibleMinimumThreshold, static_cast(statistics->GetScalarValueMin())); m_SensibleMaximumThreshold = std::max(m_SensibleMaximumThreshold, static_cast(statistics->GetScalarValueMax())); } if (m_LockedUpperThreshold) { m_LowerThreshold = (m_SensibleMaximumThreshold + m_SensibleMinimumThreshold) / 2.0; m_UpperThreshold = m_SensibleMaximumThreshold; } else { double range = m_SensibleMaximumThreshold - m_SensibleMinimumThreshold; m_LowerThreshold = m_SensibleMinimumThreshold + range / 3.0; m_UpperThreshold = m_SensibleMinimumThreshold + 2 * range / 3.0; } bool isFloatImage = false; if ((referenceImage->GetPixelType().GetPixelType() == itk::IOPixelEnum::SCALAR) && (referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::FLOAT || referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::DOUBLE)) { isFloatImage = true; } IntervalBordersChanged.Send(m_SensibleMinimumThreshold, m_SensibleMaximumThreshold, isFloatImage); ThresholdingValuesChanged.Send(m_LowerThreshold, m_UpperThreshold); } } void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage) { AccessByItk_n(inputAtTimeStep, ITKThresholding, (previewImage, timeStep)); } } template void mitk::BinaryThresholdBaseTool::ITKThresholding(const itk::Image* inputImage, - Image* segmentation, + LabelSetImage *segmentation, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; + const auto activeValue = this->GetActiveLabelValueOfPreview(); + this->SetSelectedLabels({activeValue}); + typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(inputImage); filter->SetLowerThreshold(m_LowerThreshold); filter->SetUpperThreshold(m_UpperThreshold); - filter->SetInsideValue(this->GetUserDefinedActiveLabel()); + filter->SetInsideValue(activeValue); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index cac6e3f6af..ff8940d165 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,77 +1,78 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBinaryThresholdBaseTool_h #define mitkBinaryThresholdBaseTool_h #include #include #include #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public SegWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; mitkClassMacro(BinaryThresholdBaseTool, SegWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThreshold, ScalarType); itkGetMacro(SensibleMaximumThreshold, ScalarType); void InitiateToolByInput() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; template void ITKThresholding(const itk::Image* inputImage, - Image* segmentation, unsigned int timeStep); + LabelSetImage *segmentation, + unsigned int timeStep); private: ScalarType m_SensibleMinimumThreshold; ScalarType m_SensibleMaximumThreshold; ScalarType m_LowerThreshold; ScalarType m_UpperThreshold; /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index 84ab8d6774..688412dd2e 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,103 +1,114 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" #include #include // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } +mitk::OtsuTool3D::OtsuTool3D() + : SegWithPreviewTool() +{ + this->ResetsToEmptyPreviewOn(); + this->UseSpecialPreviewColorOff(); +} + void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu.svg"); return resource; } const char* mitk::OtsuTool3D::GetName() const { return "Otsu"; } void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { int numberOfThresholds = m_NumberOfRegions - 1; mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(m_UseValley); otsuFilter->SetNumberOfBins(m_NumberOfBins); otsuFilter->SetInput(inputAtTimeStep); otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } auto otsuResultImage = otsuFilter->GetOutput(); mitk::ImageReadAccessor newMitkImgAcc(otsuResultImage); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::OtsuTool3D::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); auto labelset = preview->GetLabelSet(preview->GetActiveLayer()); - labelset->RemoveAllLabels(); + for (LabelSetImage::GroupIndexType i = 0; iGetNumberOfLayers(); ++i) + { + preview->GetLabelSet(i)->RemoveAllLabels(); + } + for (unsigned int i = 0; i < m_NumberOfRegions; ++i) { auto label = LabelSetImageHelper::CreateNewLabel(preview, "Otsu"); label->SetValue(i + 1); labelset->AddLabel(label, false); } } unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index 7185fed298..b93690340a 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,65 +1,65 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkOtsuTool3D_h #define mitkOtsuTool3D_h #include "mitkSegWithPreviewTool.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; class MITKSEGMENTATION_EXPORT OtsuTool3D : public SegWithPreviewTool { public: mitkClassMacro(OtsuTool3D, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; itkSetMacro(NumberOfBins, unsigned int); itkGetConstMacro(NumberOfBins, unsigned int); itkSetMacro(NumberOfRegions, unsigned int); itkGetConstMacro(NumberOfRegions, unsigned int); itkSetMacro(UseValley, bool); itkGetConstMacro(UseValley, bool); itkBooleanMacro(UseValley); /**Returns the number of max bins based on the current input image.*/ unsigned int GetMaxNumberOfBins() const; protected: - OtsuTool3D() = default; + OtsuTool3D(); ~OtsuTool3D() = default; void UpdatePrepare() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; unsigned int m_NumberOfBins = 128; unsigned int m_NumberOfRegions = 2; bool m_UseValley = false; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 96afc90601..6478b66ed6 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,256 +1,259 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Picking.svg"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { - AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), mitk::LabelSetImage::UnlabeledValue, emptyTimeStep)); + const auto activeValue = this->GetActiveLabelValueOfPreview(); + this->SetSelectedLabels({activeValue}); + + AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), activeValue, mitk::LabelSetImage::UnlabeledValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 9b89cc0b21..ca649187e3 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,778 +1,818 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); - auto* activeLayer = newPreviewImage->GetActiveLabelSet(); - auto* activeLabel = activeLayer->GetActiveLabel(); - if (m_UseSpecialPreviewColor) + auto* activeLabelSet = newPreviewImage->GetActiveLabelSet(); + if (nullptr == activeLabelSet) + { + newPreviewImage->AddLayer(); + activeLabelSet = newPreviewImage->GetActiveLabelSet(); + } + + auto* activeLabel = activeLabelSet->GetActiveLabel(); + if (nullptr == activeLabel) + { + activeLabel = activeLabelSet->AddLabel("toolresult", previewColor); + activeLabelSet = newPreviewImage->GetActiveLabelSet(); + activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); + } + else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); - activeLayer->UpdateLookupTable(activeLabel->GetValue()); + activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); } activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } - LabelMappingType labelMapping = { { this->GetUserDefinedActiveLabel(),this->GetUserDefinedActiveLabel() } }; + LabelMappingType labelMapping = {}; - if (LabelTransferScope::SelectedLabels == this->m_LabelTransferScope) - { - labelMapping.clear(); - for (auto label : this->m_SelectedLabels) - { - labelMapping.push_back({ label, label+offset }); - } - } - else if (LabelTransferScope::AllLabels == this->m_LabelTransferScope) + switch (this->m_LabelTransferScope) { - labelMapping.clear(); - const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue()+offset }); - } + case LabelTransferScope::SelectedLabels: + { + for (auto label : this->m_SelectedLabels) + { + labelMapping.push_back({label, label + offset}); + } + } + break; + case LabelTransferScope::AllLabels: + { + const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + labelMapping.push_back({labelIter->second->GetValue(), labelIter->second->GetValue() + offset}); + } + } + break; + default: + { + if (m_SelectedLabels.empty()) + mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " + "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " + "implementation of derived tool class."; + if (m_SelectedLabels.size() > 1) + mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " + "LabelTransferScope==ActiveLabel but more then one selected label is indicated." + "Should be only one. Check implementation of derived tool class."; + labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); + } + break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } - catch (...) + catch (mitk::Exception& e) { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - throw; + Tool::ErrorMessage(e.GetDescription()); + mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue != sourceLabel && LabelSetImage::UnlabeledValue != targetLabel && !target->ExistLabel(targetLabel, target->GetActiveLayer())) { if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; } auto clonedLabel = source->GetLabel(sourceLabel)->Clone(); clonedLabel->SetValue(targetLabel); target->GetActiveLabelSet()->AddLabel(clonedLabel); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } +mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const +{ + const auto previewImage = this->GetPreviewSegmentation(); + const auto activeLabel = previewImage->GetActiveLabel(previewImage->GetActiveLayer()); + if (nullptr == activeLabel) + mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " + "preview has no active label indicated. Check " + "implementation of the class."; + + return activeLabel->GetValue(); +} + const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; const auto labelSet = source->GetActiveLabelSet(); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h index 3369d6be4d..a4141de0b1 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h @@ -1,314 +1,321 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegWithPreviewTool_h #define mitkSegWithPreviewTool_h #include "mitkTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT SegWithPreviewTool : public Tool { public: mitkClassMacro(SegWithPreviewTool, Tool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); itkSetMacro(ResetsToEmptyPreview, bool); itkGetMacro(ResetsToEmptyPreview, bool); itkBooleanMacro(ResetsToEmptyPreview); itkSetMacro(UseSpecialPreviewColor, bool); itkGetMacro(UseSpecialPreviewColor, bool); itkBooleanMacro(UseSpecialPreviewColor); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle); itkGetMacro(MergeStyle, MultiLabelSegmentation::MergeStyle); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle); itkGetMacro(OverwriteStyle, MultiLabelSegmentation::OverwriteStyle); enum class LabelTransferScope { - ActiveLabel, //Only the active label will be transfered from preview to segmentation. + ActiveLabel, //Only the selected label will be transfered from the preview segmentation + //to the result segmentation. + //If this mode is selected the class expects that GetSelectedLabels indicate + //the label in the preview. SelectedLabels, //The labels defined as selected labels will be transfered. AllLabels //Transfer all labels of the preview }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferScope(LabelTransferScope labelTransferScope); itkGetMacro(LabelTransferScope, LabelTransferScope); using SelectedLabelVectorType = std::vector; /** Specifies the labels that should be transfered form preview to the working image, - if the segmentation is confirmed. The setting will be used, if LabelTransferScope is set to "SelectedLabels". - The selected label IDs corespond to the labels of the preview image.*/ + if the segmentation is confirmed. The setting will be used, if LabelTransferScope is set to "ActiveLabel" + or "SelectedLabels". + @remark If the LabelTransferScope=="ActiveLabel", the class expects only one label to be selected. + @remark The selected label IDs corespond to the labels of the preview image.*/ void SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer); itkGetMacro(SelectedLabels, SelectedLabelVectorType); enum class LabelTransferMode { MapLabel, //Only the active label will be transfered from preview to segmentation. AddLabel //The labels defined as selected labels will be transfered. }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferMode(LabelTransferMode labelTransferMode); itkGetMacro(LabelTransferMode, LabelTransferMode); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ bool IsUpdating() const; /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Returns the currently selected segmentation node * @return a mitk::DataNode which contains a segmentation image */ virtual DataNode* GetTargetSegmentationNode() const; LabelSetImage* GetTargetSegmentation() const; /** Returns the image that contains the preview of the current segmentation. * Returns null if the node is not set or does not contain an image.*/ LabelSetImage* GetPreviewSegmentation(); const LabelSetImage* GetPreviewSegmentation() const; DataNode* GetPreviewSegmentationNode(); protected: ToolCommand::Pointer m_ProgressCommand; SegWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~SegWithPreviewTool() override; const char* GetGroup() const override; /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimeStep(const Image* image, TimeStepType timestep); /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::Pointer GetImageByTimeStep(Image* image, TimeStepType timestep); /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); void EnsureTargetSegmentationNodeInDataStorage() const; /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); /** This member function offers derived classes the possibility to alter what should happen directly before the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdatePrepare(); /** This member function offers derived classes the possibility to alter what should happen directly after the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdateCleanUp(); using LabelMappingType = std::vector >; /** This member function offers derived classes the possibility to alter what should happen directly before the content of the preview is transfered to the segmentation, when the segmentation is confirmed. It is called by CreateResultSegmentationFromPreview. Default implementation ensure that all labels that will be transfered, exist in the segmentation. If they are not existing before the transfer, the will be added by cloning the label information of the preview. @param labelMapping the mapping that should be used for transfering the labels. */ virtual void PreparePreviewToResultTransfer(const LabelMappingType& labelMapping); static void TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target); /**Helper function that can be used to move the content of an LabelSetImage (the pixels of the active source layer and the labels). This is e.g. helpfull if you generate an LabelSetImage content in DoUpdatePreview and you want to transfer it into the preview image.*/ static void TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep); /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. * It also provides the current/old segmentation at the time point, * which can be used, if the preview depends on the the segmenation so far. */ virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) = 0; /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool @remark Calling this function will generate a new preview image, and the old might be invalidated. Therefore this function should not be used within the scope of UpdatePreview (m_IsUpdating == true).*/ void ResetPreviewNode(); /** Resets the complete content of the preview image. The instance of the preview image and its settings * stay the same.*/ void ResetPreviewContent(); /** Resets only the image content of the specified timeStep of the preview image. If the preview image or the specified time step does not exist, nothing happens.*/ void ResetPreviewContentAtTimeStep(unsigned int timeStep); TimePointType GetLastTimePointOfUpdate() const; + LabelSetImage::LabelValueType GetActiveLabelValueOfPreview() const; + itkGetConstMacro(UserDefinedActiveLabel, Label::PixelType); itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /**Internal helper that ensures that the stored active label is up to date. This is a fix for T28131 / T28986. It should be refactored if T28524 is being worked on. On the long run, the active label will be communicated/set by the user/toolmanager as a state of the tool and the tool should react accordingly (like it does for other external state changes). @return indicates if the label has changed (true) or not. */ bool EnsureUpToDateUserDefinedActiveLabel(); /**Returns that label mapping between preview segmentation (first element of pair) and result segmentation (second element of pair). The content depends on the settings of LabelTransferMode and LabelTransferScope*/ LabelMappingType GetLabelMapping() const; /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; /** Controls if ResetPreviewNode generates an empty content (true) or clones the current segmentation (false).*/ bool m_ResetsToEmptyPreview = false; /** Controls if for the preview of the active label a special preview color is used. * If set to false, coloring will stay in the preview like it is in the working image.*/ bool m_UseSpecialPreviewColor = true; TimePointType m_LastTimePointOfUpdate = 0.; bool m_IsUpdating = false; Label::PixelType m_UserDefinedActiveLabel = 1; /** This variable indicates if for the tool a working plane geometry is defined. * If a working plane is defined the tool will only work an the slice of the input * and the segmentation. Thus only the relevant input slice will be passed to * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when * ConfirmSegmentation() is called.*/ PlaneGeometry::Pointer m_WorkingPlaneGeometry; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::MergeStyle. */ MultiLabelSegmentation::MergeStyle m_MergeStyle = MultiLabelSegmentation::MergeStyle::Replace; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::OverwriteStyle. */ MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = MultiLabelSegmentation::OverwriteStyle::RegardLocks; LabelTransferScope m_LabelTransferScope = LabelTransferScope::ActiveLabel; SelectedLabelVectorType m_SelectedLabels = {}; LabelTransferMode m_LabelTransferMode = LabelTransferMode::MapLabel; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index 908f933b7e..734d9ca225 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,369 +1,371 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; IOUtil::Save(inputAtTimeStep, inputImagePath); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { outputImagePath = outDir; } this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); if (isSubTask) { // Run total segmentator again this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { Image::Pointer outputImage = IOUtil::Load(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); - auto labelset = preview->GetLabelSet(preview->GetActiveLayer()); - labelset->RemoveAllLabels(); + for (LabelSetImage::GroupIndexType i = 0; i < preview->GetNumberOfLayers(); ++i) + { + preview->GetLabelSet(i)->RemoveAllLabels(); + } if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); newlayer->SetLayer(0); aggloLabelImage->AddLayer(newlayer); for (auto const &outputImagePath : filePaths) { double rgba[4]; aggloLabelImage->GetActiveLabelSet()->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); aggloLabelImage->GetActiveLabelSet()->AddLabel(label); Image::Pointer outputImage = IOUtil::Load(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); auto labelSet = aggloLabelImage->GetActiveLabelSet(); mitk::TransferLabelContent(source, aggloLabelImage, labelSet, 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; #if defined(__APPLE__) || defined(_WIN32) command = "python"; #endif args.clear(); #ifdef _WIN32 std::string ending = "Scripts"; if (0 == this->GetPythonPath().compare(this->GetPythonPath().length() - ending.length(), ending.length(), ending)) { args.push_back("TotalSegmentator"); } else { args.push_back("Scripts/TotalSegmentator"); } #endif #if defined(__APPLE__) args.push_back("TotalSegmentator"); #endif args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { if (line > 1 && line < 106) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { m_LabelMapTotal[std::stoi(key)] = val; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map &labelMap) { auto labelset = dest->GetLabelSet(); auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); labelset->AddLabel(label, false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; std::filesystem::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index 49b6b589b3..d2e923b8e0 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,314 +1,323 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitknnUnetTool.h" #include "mitkIOUtil.h" #include "mitkProcessExecutor.h" #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); } mitk::nnUNetTool::~nnUNetTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } void mitk::nnUNetTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); + this->SetLabelTransferMode(LabelTransferMode::AddLabel); } void mitk::nnUNetTool::RenderOutputBuffer() { if (m_OutputBuffer != nullptr) { try { if (nullptr != this->GetPreviewSegmentationNode()) { auto previewImage = this->GetPreviewSegmentation(); previewImage->InitializeByLabeledImage(m_OutputBuffer); } } catch (const mitk::Exception &e) { MITK_INFO << e.GetDescription(); } } } void mitk::nnUNetTool::SetOutputBuffer(LabelSetImage::Pointer segmentation) { m_OutputBuffer = segmentation; } mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() { return m_OutputBuffer; } void mitk::nnUNetTool::ClearOutputBuffer() { m_OutputBuffer = nullptr; } us::ModuleResource mitk::nnUNetTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char **mitk::nnUNetTool::GetXPM() const { return nullptr; } const char *mitk::nnUNetTool::GetName() const { return "nnUNet"; } mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() { return this->GetToolManager()->GetDataStorage(); } mitk::DataNode *mitk::nnUNetTool::GetRefNode() { return this->GetToolManager()->GetReferenceData(0); } +void mitk::nnUNetTool::UpdatePrepare() +{ + Superclass::UpdatePrepare(); + auto preview = this->GetPreviewSegmentation(); + auto labelset = preview->GetLabelSet(preview->GetActiveLayer()); + labelset->RemoveAllLabels(); +} + namespace { void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } } // namespace void mitk::nnUNetTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType /*timeStep*/) { if (this->GetMitkTempDir().empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-nnunet-XXXXXX")); } std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); ProcessExecutor::ArgumentListType args; inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, m_TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); if (this->GetNoPip()) { scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; } try { if (this->GetMultiModal()) { const std::string fileFormat(".nii.gz"); const std::string fileNamePart("_000_000"); std::string outModalFile; size_t len = inDir.length() + 1 + token.length() + fileNamePart.length() + 1 + fileFormat.length(); outModalFile.reserve(len); // The 1(s) indicates a directory separator char and an underscore. for (size_t i = 0; i < m_OtherModalPaths.size(); ++i) { mitk::Image::ConstPointer modalImage = m_OtherModalPaths[i]; outModalFile.append(inDir); outModalFile.push_back(IOUtil::GetDirectorySeparator()); outModalFile.append(token); outModalFile.append(fileNamePart); outModalFile.append(std::to_string(i)); outModalFile.append(fileFormat); IOUtil::Save(modalImage.GetPointer(), outModalFile); outModalFile.clear(); } } else { IOUtil::Save(inputAtTimeStep, inputImagePath); } } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } // Code calls external process std::string command = "nnUNet_predict"; if (this->GetNoPip()) { #ifdef _WIN32 command = "python"; #else command = "python3"; #endif } for (ModelParams &modelparam : m_ParamQ) { outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; modelparam.outputDir = outDir; args.clear(); if (this->GetNoPip()) { args.push_back(scriptPath); } args.push_back("-i"); args.push_back(inDir); args.push_back("-o"); args.push_back(outDir); args.push_back("-t"); args.push_back(modelparam.task); if (modelparam.model.find("cascade") != std::string::npos) { args.push_back("-ctr"); } else { args.push_back("-tr"); } args.push_back(modelparam.trainer); args.push_back("-m"); args.push_back(modelparam.model); args.push_back("-p"); args.push_back(modelparam.planId); if (!modelparam.folds.empty()) { args.push_back("-f"); for (auto fold : modelparam.folds) { args.push_back(fold); } } args.push_back("--num_threads_nifti_save"); args.push_back("1"); // fixing to 1 if (!this->GetMirror()) { args.push_back("--disable_tta"); } if (!this->GetMixedPrecision()) { args.push_back("--disable_mixed_precision"); } if (this->GetEnsemble()) { args.push_back("--save_npz"); } try { std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); itksys::SystemTools::PutEnv(cudaEnv.c_str()); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); return; } } if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) { args.clear(); command = "nnUNet_ensemble"; outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; args.push_back("-f"); for (ModelParams &modelparam : m_ParamQ) { args.push_back(modelparam.outputDir); } args.push_back("-o"); args.push_back(outDir); if (!this->GetPostProcessingJsonDirectory().empty()) { args.push_back("-pp"); args.push_back(this->GetPostProcessingJsonDirectory()); } spExec->Execute(this->GetPythonPath(), command, args); } try { Image::Pointer outputImage = IOUtil::Load(outputImagePath); previewImage->InitializeByLabeledImage(outputImage); previewImage->SetGeometry(inputAtTimeStep->GetGeometry()); m_InputBuffer = inputAtTimeStep; m_OutputBuffer = mitk::LabelSetImage::New(); m_OutputBuffer->InitializeByLabeledImage(outputImage); m_OutputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h index af8f1f6465..e6e3fc9bb1 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.h +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -1,214 +1,215 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitknnUnetTool_h #define mitknnUnetTool_h #include "mitkSegWithPreviewTool.h" #include "mitkCommon.h" #include "mitkToolManager.h" #include #include #include #include namespace us { class ModuleResource; } namespace mitk { /** * @brief nnUNet parameter request object holding all model parameters for input. * Also holds output temporary directory path. */ struct ModelParams { std::string task; std::vector folds; std::string model; std::string trainer; std::string planId; std::string outputDir; std::string inputName; std::string timeStamp; size_t generateHash() const { std::string toHash; std::string foldsConcatenated = std::accumulate(folds.begin(), folds.end(), std::string("")); toHash += this->task; toHash += this->model; toHash += this->inputName; toHash += foldsConcatenated; toHash += this->timeStamp; size_t hashVal = std::hash{}(toHash); return hashVal; } }; /** \brief nnUNet segmentation tool. \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ class MITKSEGMENTATION_EXPORT nnUNetTool : public SegWithPreviewTool { public: mitkClassMacro(nnUNetTool, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; void Activated() override; itkSetMacro(nnUNetDirectory, std::string); itkGetConstMacro(nnUNetDirectory, std::string); itkSetMacro(ModelDirectory, std::string); itkGetConstMacro(ModelDirectory, std::string); itkSetMacro(PythonPath, std::string); itkGetConstMacro(PythonPath, std::string); itkSetMacro(MitkTempDir, std::string); itkGetConstMacro(MitkTempDir, std::string); itkSetMacro(PostProcessingJsonDirectory, std::string); itkGetConstMacro(PostProcessingJsonDirectory, std::string); itkSetMacro(MixedPrecision, bool); itkGetConstMacro(MixedPrecision, bool); itkBooleanMacro(MixedPrecision); itkSetMacro(Mirror, bool); itkGetConstMacro(Mirror, bool); itkBooleanMacro(Mirror); itkSetMacro(MultiModal, bool); itkGetConstMacro(MultiModal, bool); itkBooleanMacro(MultiModal); itkSetMacro(NoPip, bool); itkGetConstMacro(NoPip, bool); itkBooleanMacro(NoPip); itkSetMacro(Ensemble, bool); itkGetConstMacro(Ensemble, bool); itkBooleanMacro(Ensemble); itkSetMacro(Predict, bool); itkGetConstMacro(Predict, bool); itkBooleanMacro(Predict); itkSetMacro(GpuId, unsigned int); itkGetConstMacro(GpuId, unsigned int); /** * @brief vector of ModelParams. * Size > 1 only for ensemble prediction. */ std::vector m_ParamQ; /** * @brief Holds paths to other input image modalities. * */ std::vector m_OtherModalPaths; mitk::Image::ConstPointer m_InputBuffer; /** * @brief Renders the output LabelSetImage. * To called in the main thread. */ void RenderOutputBuffer(); /** * @brief Get the Output Buffer object * * @return LabelSetImage::Pointer */ LabelSetImage::Pointer GetOutputBuffer(); /** * @brief Sets the outputBuffer to nullptr * */ void ClearOutputBuffer(); /** * @brief Returns the DataStorage from the ToolManager */ mitk::DataStorage *GetDataStorage(); mitk::DataNode *GetRefNode(); void SetOutputBuffer(LabelSetImage::Pointer); protected: /** * @brief Construct a new nnUNet Tool object. * */ nnUNetTool() = default; /** * @brief Destroy the nnUNet Tool object and deletes the temp directory. * */ ~nnUNetTool(); /** * @brief Overriden method from the tool manager to execute the segmentation * Implementation: * 1. Saves the inputAtTimeStep in a temporary directory. * 2. Copies other modalities, renames and saves in the temporary directory, if required. * 3. Sets RESULTS_FOLDER and CUDA_VISIBLE_DEVICES variables in the environment. * 3. Iterates through the parameter queue (m_ParamQ) and executes "nnUNet_predict" command with the parameters * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as * LabelSetImage and sets to previewImage. * * @param inputAtTimeStep * @param oldSegAtTimeStep * @param previewImage * @param timeStep */ void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; + void UpdatePrepare() override; private: std::string m_MitkTempDir; std::string m_nnUNetDirectory; std::string m_ModelDirectory; std::string m_PythonPath; std::string m_PostProcessingJsonDirectory; // bool m_UseGPU; kept for future // bool m_AllInGPU; bool m_MixedPrecision; bool m_Mirror; bool m_NoPip; bool m_MultiModal; bool m_Ensemble = false; bool m_Predict; LabelSetImage::Pointer m_OutputBuffer; unsigned int m_GpuId; const std::string m_TEMPLATE_FILENAME = "XXXXXX_000_0000.nii.gz"; }; } // namespace mitk #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h index f394558ae0..dac754e16e 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h @@ -1,68 +1,70 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkEditableContourToolGUIBase_h #define QmitkEditableContourToolGUIBase_h #include #include class QButtonGroup; namespace mitk { class EditableContourTool; } namespace Ui { class QmitkEditableContourToolGUIControls; } /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::EditableContourTool based classes. \sa mitk::LassoTool */ class MITKSEGMENTATIONUI_EXPORT QmitkEditableContourToolGUIBase : public QmitkToolGUI { Q_OBJECT public: + /// \cond enum class MITKSEGMENTATIONUI_EXPORT Mode { Add, Subtract }; + /// \endcond mitkClassMacro(QmitkEditableContourToolGUIBase, QmitkToolGUI); itkFactorylessNewMacro(Self); protected slots: void OnNewToolAssociated(mitk::Tool*); void OnConfirmSegmentation(); void OnClearContour(); void OnAutoConfirm(bool on); void OnModeToggled(Mode mode); void OnShowInformation(bool on); protected: QmitkEditableContourToolGUIBase(); ~QmitkEditableContourToolGUIBase() override; Ui::QmitkEditableContourToolGUIControls* m_Controls; QButtonGroup* m_ModeButtonGroup; itk::SmartPointer m_NewTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui index b408230b03..00fe89030a 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui @@ -1,98 +1,95 @@ QmitkMultiLabelInspector 0 0 400 - 120 + 200 0 0 Form - - 0 - 0 0 0 0 0 0 0 - 120 + 200 16777215 - 120 + 200 QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked false false QAbstractItemView::SingleSelection QAbstractItemView::SelectRows false 10 50 false QmitkMultiLabelTreeView QTreeView
QmitkMultiLabelTreeView.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 09f0675a1c..df94a29994 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,2007 +1,2007 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include // Includes for the merge operation #include "mitkImageToContourFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map QmitkSlicesInterpolator::createActionToSlicer(const QList& windows) { std::map actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } // Check whether the given contours are coplanar bool AreContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) { // Here we check two things: // 1. Whether the normals of both contours are at least parallel // 2. Whether both contours lie in the same plane // Check for coplanarity: // a. Span a vector between two points one from each contour // b. Calculate dot product for the vector and one of the normals // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar double vec[3]; vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; double n[3]; n[0] = rightHandSide.ContourNormal[0]; n[1] = rightHandSide.ContourNormal[1]; n[2] = rightHandSide.ContourNormal[2]; double dot = vtkMath::Dot(n, vec); double n2[3]; n2[0] = leftHandSide.ContourNormal[0]; n2[1] = leftHandSide.ContourNormal[1]; n2[2] = leftHandSide.ContourNormal[2]; // The normals of both contours have to be parallel but not of the same orientation double lengthLHS = leftHandSide.ContourNormal.GetNorm(); double lengthRHS = rightHandSide.ContourNormal.GetNorm(); double dot2 = vtkMath::Dot(n, n2); bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) return true; else return false; } mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, const mitk::PlaneGeometry * contourPlane, unsigned int timeStep) { vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(contourPlane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Update(); mitk::Image::Pointer slice = extractor->GetOutput(); return slice; } template std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) { std::vector pixelsPresent; mitk::ImagePixelReadAccessor readAccessor(labelSetImage); std::size_t numberOfPixels = 1; for (size_t dim = 0; dim < VImageDimension; ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { mitk::Label::PixelType pixelVal = *(src + i); if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != mitk::LabelSetImage::UnlabeledValue) ) pixelsPresent.push_back(pixelVal); } return pixelsPresent; } template ModifyLabelActionTrigerred ModifyLabelProcessing(mitk::LabelSetImage* labelSetImage, mitk::SurfaceInterpolationController::Pointer surfaceInterpolator, unsigned int timePoint) { auto currentLayerID = labelSetImage->GetActiveLayer(); auto numTimeSteps = labelSetImage->GetTimeSteps(); ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; auto* currentContourList = surfaceInterpolator->GetContours(timePoint, currentLayerID); - if (nullptr == currentContourList) + while (nullptr == currentContourList) { surfaceInterpolator->OnAddLayer(); currentContourList = surfaceInterpolator->GetContours(timePoint, currentLayerID); } mitk::LabelSetImage::Pointer labelSetImage2 = labelSetImage->Clone(); mitk::ImagePixelReadAccessor readAccessor(labelSetImage2.GetPointer()); for (auto& contour : *currentContourList) { mitk::Label::PixelType contourPixelValue; itk::Index<3> itkIndex; labelSetImage2->GetGeometry()->WorldToIndex(contour.ContourPoint, itkIndex); if (VImageDimension == 4) { itk::Index time3DIndex; for (size_t i = 0; i < itkIndex.size(); ++i) time3DIndex[i] = itkIndex[i]; time3DIndex[3] = timePoint; contourPixelValue = readAccessor.GetPixelByIndexSafe(time3DIndex); } else if (VImageDimension == 3) { itk::Index geomIndex; for (size_t i = 0; i < itkIndex.size(); ++i) geomIndex[i] = itkIndex[i]; contourPixelValue = readAccessor.GetPixelByIndexSafe(geomIndex); } if (contour.LabelValue != contourPixelValue) { if (contourPixelValue == 0) // Erase label { for (size_t t = 0; t < numTimeSteps; ++t) surfaceInterpolator->RemoveContours(contour.LabelValue, t, currentLayerID); actionTriggered = ModifyLabelActionTrigerred::Erase; } else { contour.LabelValue = contourPixelValue; actionTriggered = ModifyLabelActionTrigerred::Merge; } } } return actionTriggered; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_PreviousActiveLabelValue(0), m_CurrentActiveLabelValue(0), m_PreviousLayerIndex(0), m_CurrentLayerIndex(0), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); // T28261 // m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); // vboxLayout->addWidget(m_BtnSuggestPlane); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicer = createActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } for (auto* slicer : m_ControllerToTimeObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } this->ClearSegmentationObservers(); m_ActionToSlicer.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); m_SurfaceInterpolator->UnsetSelectedImage(); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); // T28261 // m_BtnSuggestPlane->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { this->ClearSegmentationObservers(); if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); try { if (m_SegmentationObserverTags.find(labelSetImage) == m_SegmentationObserverTags.end()) { auto command2 = itk::MemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnModifyLabelChanged); auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_SegmentationObserverTags[workingImage] = workingImage->AddObserver(itk::ModifiedEvent(), command2); } } catch (const std::exception& e) { MITK_ERROR << "Error casting node data to LabelSetImage\n"; } } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); m_CmbInterpolation->setCurrentIndex(0); return; } // Updating the current selected segmentation for the 3D interpolation this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (timePoint != m_SurfaceInterpolator->GetCurrentTimePoint()) { m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_3DInterpolationEnabled) { m_3DContourNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) { Interpolate(plane, m_TimePoints[slicer], slicer); } return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } void QmitkSlicesInterpolator::OnLayerChanged() { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { m_3DContourNode->SetData(nullptr); this->Show3DInterpolationResult(false); } if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); if (m_Segmentation) { if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::PlaneGeometry::Pointer slicingPlane = mitk::PlaneGeometry::New(); mitk::Vector3D slicingPlaneNormalVector; FillVector3D(slicingPlaneNormalVector,0.0,1.0,0.0); mitk::Point3D origin; FillVector3D(origin, 0.0, 0.0, 0.0); slicingPlane->InitializePlane(origin, slicingPlaneNormalVector); if (interpolatedSurface.IsNotNull() && workingNode) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } m_SurfaceInterpolator->ReinitializeInterpolation(); } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast(workingNode->GetData()); if (nullptr == segmentationImage) return; const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); auto labelSet = segmentationImage->GetActiveLabelSet(); auto activeValue = labelSet->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, labelSet, timeStep, 0, mitk::LabelSetImage::UnlabeledValue, false, { {0, mitk::LabelSetImage::UnlabeledValue}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; const auto timePoint = slicer->GetSelectedTimePoint(); if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(timePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); // const auto numThreads = 1; std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); // Do the interpolation here. for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { interpolate(threadIndex); } m_Interpolator->DisableSliceImageCache(); const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabelSet()->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicer.begin(); it != m_ActionToSlicer.end(); ++it) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); auto activeLabelColor = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(timePoint) || !segmentationGeometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(timePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, labelSetImage->GetActiveLabelSet(), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); // m_CmbInterpolation->setCurrentIndex(0); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { std::vector contourPlanes; std::vector contourList; if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto activeLayerID = labelSetImage->GetActiveLayer(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } // Adding layer, label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto layerID = dynamic_cast(contourNode->GetProperty("layerID"))->GetValue(); auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); auto px = dynamic_cast(contourNode->GetProperty("px"))->GetValue(); auto py = dynamic_cast(contourNode->GetProperty("py"))->GetValue(); auto pz = dynamic_cast(contourNode->GetProperty("pz"))->GetValue(); // auto layerImage = labelSetImage->GetLayerImage(layerID); auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); labelSetImage->SetActiveLayer(layerID); auto sliceImage = ExtractSliceFromImage(labelSetImage, planeGeometry, timeStep); labelSetImage->SetActiveLayer(activeLayerID); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; vtkSmartPointer intArray = vtkSmartPointer::New(); intArray->InsertNextValue(labelID); intArray->InsertNextValue(layerID); intArray->InsertNextValue(timeStep); contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); vtkSmartPointer doubleArray = vtkSmartPointer::New(); doubleArray->InsertNextValue(px); doubleArray->InsertNextValue(py); doubleArray->InsertNextValue(pz); contour->GetVtkPolyData()->GetFieldData()->AddArray(doubleArray); contour->DisconnectPipeline(); contourList.push_back(contour); contourPlanes.push_back(planeGeometry); } labelSetImage->SetActiveLayer(activeLayerID); // size_t activeLayer = labelSetImage->GetActiveLayer(); for (size_t l = 0; l < labelSetImage->GetNumberOfLayers(); ++l) { this->OnAddLabelSetConnection(l); } // labelSetImage->SetActiveLayer(activeLayer); m_SurfaceInterpolator->CompleteReinitialization(contourList, contourPlanes); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicer.find(action); if (iter != m_ActionToSlicer.end()) { mitk::SliceNavigationController *slicer = iter->second; AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); // additional error message on std::cerr std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } auto* activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel(); auto* segmentation = dynamic_cast(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue(), true, 0); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } void QmitkSlicesInterpolator::PrepareInputsFor3DInterpolation() { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { auto *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { int ret = QMessageBox::Yes; if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) { QMessageBox msgBox; msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); ret = msgBox.exec(); } auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { // Maybe set the segmentation node here m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } else { m_CmbInterpolation->setCurrentIndex(0); } } else { QWidget::setEnabled(false); m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); // T28261 // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; try { // this->PrepareInputsFor3DInterpolation(); m_SurfaceInterpolator->Modified(); } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated this->On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_3DContourNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) return; auto* labelSetImage = dynamic_cast(workingNode->GetData()); auto* label = labelSetImage->GetActiveLabelSet()->GetActiveLabel(); if (label == nullptr) return; m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(label->GetValue()); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); try{ auto labelSetImage = dynamic_cast(workingNode->GetData()); for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) { this->OnAddLabelSetConnection(layerID); } } catch (std::exception &e) { MITK_ERROR << e.what() << "\n"; } if (workingNode) { QWidget::setEnabled(true); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } // Sets up the surface interpolator to accept time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); double minSpacing = 100; double maxSpacing = 0; for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image::Pointer segmentationImage; segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) { auto allRenderWindows = mitk::BaseRenderer::GetAll3DRenderWindows(); for (auto mapit = allRenderWindows.begin(); mapit != allRenderWindows.end(); ++mapit) { m_3DContourNode->SetVisibility(status, mapit->second); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_3DContourNode->SetData(nullptr); m_FeedbackNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); } } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } void QmitkSlicesInterpolator::OnAddLabelSetConnection(unsigned int layerID) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto labelSet = workingImage->GetLabelSet(layerID); labelSet->RemoveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnRemoveLabel); labelSet->ActiveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->AddLabelSetConnection(layerID); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnAddLabelSetConnection() { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->AddLabelSetConnection(); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnRemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) { size_t previousLayerID = labelSetImage->GetActiveLayer(); labelSetImage->SetActiveLayer(layerID); labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnRemoveLabel); labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->RemoveLabelSetConnection(labelSetImage, layerID); labelSetImage->SetActiveLayer(previousLayerID); } void QmitkSlicesInterpolator::OnRemoveLabelSetConnection() { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnRemoveLabel(mitk::Label::PixelType /*removedLabelValue*/) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto currentLayerID = labelSetImage->GetActiveLayer(); auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); for (size_t t = 0; t < numTimeSteps; ++t) { m_SurfaceInterpolator->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); } } catch(const std::exception& e) { MITK_ERROR << "Bad cast error for labelSetImage"; } } } void QmitkSlicesInterpolator::OnModifyLabelChanged(const itk::Object *caller, const itk::EventObject & /*event*/) { auto *tempImage = dynamic_cast(const_cast(caller) ) ; if( tempImage == nullptr) { MITK_ERROR << "Unable to cast caller to LabelSetImage."; return; } ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; if(m_ToolManager->GetWorkingData(0) != nullptr) { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (labelSetImage == tempImage) { const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } auto timeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto numLayersInCurrentSegmentation = m_SurfaceInterpolator->GetNumberOfLayersInCurrentSegmentation(); // This handles the add layer or remove layer operation. if (labelSetImage->GetNumberOfLayers() != numLayersInCurrentSegmentation) { bool addLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation +1) ); bool removeLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation - 1) ); m_SurfaceInterpolator->SetNumberOfLayersInCurrentSegmentation(labelSetImage->GetNumberOfLayers()); if (addLayer) { m_SurfaceInterpolator->OnAddLayer(); this->OnAddLabelSetConnection(); } if (removeLayer) { m_SurfaceInterpolator->OnRemoveLayer(); } return; } // Get the pixels present in the image. // This portion of the code deals with the merge and erase labels operations. auto imageDimension = labelSetImage->GetDimension(); if (imageDimension == 4) { actionTriggered = ModifyLabelProcessing<4>(labelSetImage, m_SurfaceInterpolator, timeStep); } else { actionTriggered = ModifyLabelProcessing<3>(labelSetImage, m_SurfaceInterpolator, timeStep); } if (actionTriggered == ModifyLabelActionTrigerred::Erase) { m_InterpolatedSurfaceNode->SetData(nullptr); } auto currentLayerID = labelSetImage->GetActiveLayer(); if (actionTriggered == ModifyLabelActionTrigerred::Merge) { this->MergeContours(timeStep, currentLayerID); m_SurfaceInterpolator->Modified(); } } } } void QmitkSlicesInterpolator::MergeContours(unsigned int timeStep, unsigned int layerID) { auto* contours = m_SurfaceInterpolator->GetContours(timeStep, layerID); if (nullptr == contours) return; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); for (size_t i = 0; i < contours->size(); ++i) { for (size_t j = i+1; j < contours->size(); ++j) { // And Labels are the same and Layers are the same. bool areContoursCoplanar = AreContoursCoplanar((*contours)[i], (*contours)[j]); if ( areContoursCoplanar && ((*contours)[i].LabelValue == (*contours)[j].LabelValue) ) { // Update the contour by re-extracting the slice from the corresponding plane. mitk::Image::Pointer slice = ExtractSliceFromImage(m_Segmentation, (*contours)[i].Plane, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(slice); contourExtractor->SetContourValue((*contours)[i].LabelValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); (*contours)[i].Contour = contour; // Update the interior point of the contour (*contours)[i].ContourPoint = m_SurfaceInterpolator->ComputeInteriorPointOfContour((*contours)[i],dynamic_cast(m_Segmentation)); // Setting the contour polygon data to an empty vtkPolyData, // as source label is empty after merge operation. (*contours)[j].Contour->SetVtkPolyData(vtkSmartPointer::New()); } } } auto segmentationNode = m_SurfaceInterpolator->GetSegmentationImageNode(); if (segmentationNode == nullptr) { MITK_ERROR << "segmentation Image Node not found\n"; } auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); // Remove empty contour nodes. auto isContourEmpty = [] (const mitk::SurfaceInterpolationController::ContourPositionInformation& contour) { return (contour.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0); }; auto it = std::remove_if((*contours).begin(), (*contours).end(), isContourEmpty); (*contours).erase(it, (*contours).end()); } void QmitkSlicesInterpolator::ClearSegmentationObservers() { auto dataIter = m_SegmentationObserverTags.begin(); while (dataIter != m_SegmentationObserverTags.end()) { auto labelSetImage = (*dataIter).first; labelSetImage->RemoveObserver((*dataIter).second); for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) { this->OnRemoveLabelSetConnection(labelSetImage, layerID); } ++dataIter; } m_SegmentationObserverTags.clear(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp index 14d53c8f2b..f400af38f7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -1,499 +1,500 @@ #include "QmitkTotalSegmentatorToolGUI.h" #include "mitkProcessExecutor.h" #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The TotalSegmentator tool can be very slow."; this->ShowErrorMessage(warning); } m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Controls.sysPythonComboBox->addItem("Select"); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->addItem("Select"); m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); m_Controls.pythonEnvComboBox->setDisabled(true); m_Controls.previewButton->setDisabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); m_Controls.subtaskComboBox->addItems(VALID_TASKS); QString welcomeText; this->SetGPUInfo(); if (m_GpuLoader.GetGPUCount() != 0) { welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; } else { welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; } connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); connect(m_Controls.pythonEnvComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); connect(m_Controls.sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") { m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); } const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); if (m_IsInstalled) { m_PythonPath = GetExactPythonPath(storageDir); m_Installer.SetVirtualEnvPath(m_PythonPath); this->EnableAll(m_IsInstalled); welcomeText += " TotalSegmentator is already found installed."; } else { welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; } this->WriteStatusMessage(welcomeText); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Controls.clearButton->setIcon(deleteIcon); m_Controls.previewButton->setIcon(arrowIcon); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } void QmitkTotalSegmentatorToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) { m_Controls.previewButton->setEnabled(isEnable); m_Controls.subtaskComboBox->setEnabled(isEnable); m_Controls.installButton->setEnabled((!isEnable)); } void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() { bool isInstalled = false; QString systemPython = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); if (systemPython.isEmpty()) { this->WriteErrorMessage("ERROR: Couldn't find Python."); } else { this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); m_Installer.SetSystemPythonPath(systemPython); isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); if (isInstalled) { m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); } else { this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); } } this->EnableAll(isInstalled); } void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } try { m_Controls.previewButton->setEnabled(false); qApp->processEvents(); if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); } bool isFast = m_Controls.fastBox->isChecked(); QString subTask = m_Controls.subtaskComboBox->currentText(); if (subTask != VALID_TASKS[0]) { isFast = true; } tool->SetPythonPath(m_PythonPath.toStdString()); tool->SetGpuId(FetchSelectedGPUFromUI()); tool->SetFast(isFast); tool->SetSubTask(subTask.toStdString()); this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); tool->UpdatePreview(); m_Controls.previewButton->setEnabled(true); m_FirstPreviewComputation = false; } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); return; } catch (...) { std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. { QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (lastSelectedPyEnv != pythonPathTextItem) { m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); } } } void QmitkTotalSegmentatorToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) { QString fullPath = pythonPath; bool isPythonExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; } #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; } #endif bool isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; return isExists; } void QmitkTotalSegmentatorToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.sysPythonComboBox->addItem("(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { m_Controls.sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); } } } } QString QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) { QString pyPath; if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.sysPythonComboBox->insertItem(0, path); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.sysPythonComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else { QString uiPyPath = this->GetPythonPathFromUI(pyEnv); pyPath = this->GetExactPythonPath(uiPyPath); } return pyPath; } void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else if (!this->IsTotalSegmentatorInstalled(pyEnv)) { this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); m_Controls.previewButton->setDisabled(true); } else {// Show positive status meeage m_Controls.previewButton->setDisabled(false); QString uiPyPath = this->GetPythonPathFromUI(pyEnv); m_PythonPath = this->GetExactPythonPath(uiPyPath); } } QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } QString QmitkTotalSegmentatorToolGUI::GetExactPythonPath(const QString &pyEnv) const { QString fullPath = pyEnv; bool isPythonExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!isPythonExists && !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); } #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!isPythonExists && !(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); } #endif if (!isPythonExists) { fullPath.clear(); } return fullPath; } void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) { bool isEnabled = false; if (state == Qt::Checked) { isEnabled = true; m_Controls.previewButton->setDisabled(true); m_PythonPath.clear(); } else { m_PythonPath.clear(); m_Controls.previewButton->setDisabled(true); if (m_IsInstalled) { const QString pythonPath = m_Installer.GetVirtualEnvPath(); m_PythonPath = this->GetExactPythonPath(pythonPath); this->EnableAll(m_IsInstalled); } } m_Controls.pythonEnvComboBox->setEnabled(isEnabled); } void QmitkTotalSegmentatorToolGUI::OnClearInstall() { QDir folderPath(m_Installer.GetVirtualEnvPath()); if (folderPath.removeRecursively()) { m_Controls.installButton->setEnabled(true); + m_IsInstalled = false; if (!m_Controls.overrideBox->isChecked()) { m_Controls.previewButton->setEnabled(false); } } else { MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; } } bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) { if (GetSystemPythonPath().isEmpty()) { return false; } QDir folderPath(GetBaseDir()); folderPath.mkdir(venvName); if (!folderPath.cd(venvName)) { return false; // Check if directory creation was successful. } mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&PrintProcessEvent); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("venv"); args.push_back(venvName.toStdString()); #ifdef _WIN32 QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; QString pythonExeFolder = "Scripts"; #else QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; QString pythonExeFolder = "bin"; #endif spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment if (folderPath.cd(pythonExeFolder)) { this->SetPythonPath(folderPath.absolutePath()); this->SetPipPath(folderPath.absolutePath()); this->InstallPytorch(); for (auto &package : PACKAGES) { this->PipInstall(package.toStdString(), &PrintProcessEvent); } std::string pythonCode; // python syntax to check if torch is installed with CUDA. pythonCode.append("import torch;"); pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " "installed WITHOUT CUDA');"); this->ExecutePython(pythonCode, &PrintProcessEvent); return true; } return false; } QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui index cb46310c3f..6d1f17d26d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui @@ -1,476 +1,476 @@ QmitknnUNetToolGUIControls 0 0 877 711 0 0 100 0 100000 100000 QmitknnUNetToolWidget 0 0 0 0 Refresh Results Folder 0 0 Python Path: 0 0 Plan: 0 0 Trainer: 0 0 nnUNet Results Folder: 0 0 Multi-Modal: 0 0 - <html><head/><body><p>Welcome to nnUNet in MITK. [Experimental]</p><p>Please note that this is only an interface to nnUNet. MITK does not ship with nnUNet. Make sure to have a working Python environment with nnUNet set up beforehand. Choose that environment in the Python Path before inferencing. </p><p>Refer to <a href="https://github.com/MIC-DKFZ/nnUNet"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/MIC-DKFZ/nnUNet</span></a> to learn everything about the nnUNet.</p><p><br/></p></body></html> + <html><head/><body><p>Welcome to nnU-Net in MITK. [Experimental]</p><p>Please note that this is only an interface to nnU-Net v1. MITK does not ship with nnU-Net v1. Make sure to have a working Python environment with nnU-Net v1 set up beforehand. Choose that environment in the Python Path before inferencing. </p><p>Refer to <a href="https://github.com/MIC-DKFZ/nnUNet"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/MIC-DKFZ/nnUNet</span></a> to learn everything about the nnU-Net.</p><p><br/></p></body></html> Qt::RichText true Configuration: 0 0 Task: 0 0 Required Modalities: Fold: 0 0 0 0 Advanced Qt::AlignRight true 5 true 0 0 0 0 6 0 0 Mixed Precision: true 0 0 GPU Id: 0 0 Enable Mirroring: true 0 0 Use Postprocessing JSON: true 0 0 Enable Caching: true Clear Cache 0 0 Cached Items: 0 0 0 Available Models: Download Stop 0 0 100000 16777215 Preview 0 0 true ctkDirectoryButton QWidget
ctkDirectoryButton.h
1
ctkComboBox QComboBox
ctkComboBox.h
1
ctkCheckableComboBox QComboBox
ctkCheckableComboBox.h
1
ctkCheckBox QCheckBox
ctkCheckBox.h
1
ctkCollapsibleGroupBox QGroupBox
ctkCollapsibleGroupBox.h
1
diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index 374f0ac5bd..f939d7a447 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,1399 +1,1406 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Check whether the given contours are coplanar bool ContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) { // Here we check two things: // 1. Whether the normals of both contours are at least parallel // 2. Whether both contours lie in the same plane // Check for coplanarity: // a. Span a vector between two points one from each contour // b. Calculate dot product for the vector and one of the normals // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar double vec[3]; vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; double n[3]; n[0] = rightHandSide.ContourNormal[0]; n[1] = rightHandSide.ContourNormal[1]; n[2] = rightHandSide.ContourNormal[2]; double dot = vtkMath::Dot(n, vec); double n2[3]; n2[0] = leftHandSide.ContourNormal[0]; n2[1] = leftHandSide.ContourNormal[1]; n2[2] = leftHandSide.ContourNormal[2]; // The normals of both contours have to be parallel but not of the same orientation double lengthLHS = leftHandSide.ContourNormal.GetNorm(); double lengthRHS = rightHandSide.ContourNormal.GetNorm(); double dot2 = vtkMath::Dot(n, n2); bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) return true; else return false; } mitk::SurfaceInterpolationController::ContourPositionInformation CreateContourPositionInformation( mitk::Surface::Pointer contour, const mitk::PlaneGeometry* planeGeometry) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.Contour = contour; mitk::ScalarType n[3]; vtkPolygon::ComputeNormal(contour->GetVtkPolyData()->GetPoints(), n); contourInfo.ContourNormal = n; contourInfo.Pos = -1; contourInfo.TimeStep = std::numeric_limits::max(); contourInfo.Plane = const_cast(planeGeometry); auto contourIntArray = vtkIntArray::SafeDownCast( contour->GetVtkPolyData()->GetFieldData()->GetAbstractArray(0) ); if (contourIntArray->GetSize() < 2) { MITK_ERROR << "In CreateContourPositionInformation. The contourIntArray is empty."; } contourInfo.LabelValue = contourIntArray->GetValue(0); contourInfo.LayerValue = contourIntArray->GetValue(1); if (contourIntArray->GetSize() >= 3) { contourInfo.TimeStep = contourIntArray->GetValue(2); } contourInfo.SliceIndex = 0; return contourInfo; }; mitk::SurfaceInterpolationController::SurfaceInterpolationController() : m_SelectedSegmentation(nullptr), m_CurrentTimePoint(0.), m_ContourIndex(0), m_ContourPosIndex(0), m_NumberOfLayersInCurrentSegmentation(0), m_PreviousActiveLabelValue(0), m_CurrentActiveLabelValue(0), m_PreviousLayerIndex(0), m_CurrentLayerIndex(0) { m_DistanceImageSpacing = 0.0; m_ReduceFilter = ReduceContourSetFilter::New(); m_NormalsFilter = ComputeContourSetNormalsFilter::New(); m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); // m_TimeSelector = ImageTimeSelector::New(); m_ReduceFilter->SetUseProgressBar(false); // m_ReduceFilter->SetProgressStepSize(1); m_NormalsFilter->SetUseProgressBar(true); m_NormalsFilter->SetProgressStepSize(1); m_InterpolateSurfaceFilter->SetUseProgressBar(true); m_InterpolateSurfaceFilter->SetProgressStepSize(7); m_Contours = Surface::New(); m_PolyData = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); m_PolyData->SetPoints(points); m_NumberOfConnectionsAdded = 0; m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { // Removing all observers this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); for (; dataIter != m_SegmentationObserverTags.end(); ++dataIter) { (*dataIter).first->RemoveObserver((*dataIter).second); } m_SegmentationObserverTags.clear(); } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } void mitk::SurfaceInterpolationController::AddNewContour(mitk::Surface::Pointer newContour) { if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { ContourPositionInformation contourInfo = CreateContourPositionInformation(newContour, nullptr); this->AddToInterpolationPipeline(contourInfo); this->Modified(); } } void mitk::SurfaceInterpolationController::AddNewContours(const std::vector& newContours, std::vector& contourPlanes, bool reinitializationAction) { if (nullptr == m_SelectedSegmentation) return; if (newContours.size() != contourPlanes.size()) { MITK_ERROR << "SurfaceInterpolationController::AddNewContours. contourPlanes and newContours are not of the same size."; } for (size_t i = 0; i < newContours.size(); ++i) { const auto &newContour = newContours[i]; const mitk::PlaneGeometry * planeGeometry = contourPlanes[i]; if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { auto contourInfo = CreateContourPositionInformation(newContour, planeGeometry); if (!reinitializationAction) { contourInfo.ContourPoint = this->ComputeInteriorPointOfContour(contourInfo, dynamic_cast(m_SelectedSegmentation) ); } else { auto vtkPolyData = contourInfo.Contour->GetVtkPolyData(); auto pointVtkArray = vtkDoubleArray::SafeDownCast(vtkPolyData->GetFieldData()->GetAbstractArray(1)); mitk::ScalarType *ptArr = new mitk::ScalarType[3]; for (int i = 0; i < pointVtkArray->GetSize(); ++i) ptArr[i] = pointVtkArray->GetValue(i); mitk::Point3D pt3D; pt3D.FillPoint(ptArr); contourInfo.ContourPoint = pt3D; } this->AddToInterpolationPipeline(contourInfo, reinitializationAction); } } this->Modified(); } mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() { DataNode* segmentationNode = nullptr; mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(m_SelectedSegmentation->GetUID()); auto dataNodeObjects = m_DataStorage->GetSubset(dataUIDPredicate); if (dataNodeObjects->Size() != 0) { for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) { segmentationNode = it->Value(); } } else { MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; } return segmentationNode; } void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) { auto planeGeometry = contourInfo.Plane; auto planeGeometryData = mitk::PlanarCircle::New(); planeGeometryData->SetPlaneGeometry(planeGeometry); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); planeGeometryData->PlaceFigure(p1); planeGeometryData->SetCurrentControlPoint(p1); planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); if (planeGeometry) { auto segmentationNode = this->GetSegmentationImageNode(); auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); auto contourFound = false; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto layerID = dynamic_cast(it->Value()->GetProperty("layerID"))->GetValue(); auto labelID = dynamic_cast(it->Value()->GetProperty("labelID"))->GetValue(); auto posID = dynamic_cast(it->Value()->GetProperty("position"))->GetValue(); bool sameLayer = (layerID == contourInfo.LayerValue); bool sameLabel = (labelID == contourInfo.LabelValue); bool samePos = (posID == contourInfo.Pos); if (samePos & sameLabel & sameLayer) { contourFound = true; it->Value()->SetData(planeGeometryData); break; } } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; return; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); // Go through the contourPlaneGeometry Data and add the segmentationNode to it. if (!contourFound) { std::string contourName = "contourPlane " + std::to_string(m_ContourIndex); auto contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); contourPlaneGeometryDataNode->SetProperty("layerID", mitk::UIntProperty::New(contourInfo.LayerValue)); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); contourPlaneGeometryDataNode->SetProperty("position", mitk::IntProperty::New(contourInfo.Pos)); contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(currentTimeStep)); contourPlaneGeometryDataNode->SetProperty("px", mitk::DoubleProperty::New(contourInfo.ContourPoint[0])); contourPlaneGeometryDataNode->SetProperty("py", mitk::DoubleProperty::New(contourInfo.ContourPoint[1])); contourPlaneGeometryDataNode->SetProperty("pz", mitk::DoubleProperty::New(contourInfo.ContourPoint[2])); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } } } void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction) { if (!m_SelectedSegmentation) return; if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } // Get current time step either from the auto GetCurrentTimeStep = [=](ContourPositionInformation contourInfo) { if (reinitializationAction) { return contourInfo.TimeStep; } return static_cast(m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint)); }; const auto currentTimeStep = GetCurrentTimeStep(contourInfo); auto GetContourLayerID = [=](ContourPositionInformation contourInfo) { unsigned int currentLayerID; if(reinitializationAction) { if (contourInfo.LayerValue == std::numeric_limits::max()) { MITK_ERROR << "In mitk::SurfaceInterpolationController::AddToInterpolationPipeline. Problem in finding layerID"; } currentLayerID = contourInfo.LayerValue; } else { try { currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); } catch (const std::exception& e) { MITK_ERROR << "Unable to cast image to LabelSetImage. " << e.what() << '\n'; } } return currentLayerID; }; unsigned int currentLayerID = GetContourLayerID(contourInfo); ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); ContourPositionInformationList ¤tContourList = currentTimeStepContoursList.at(currentLayerID); int replacementIndex = -1; int pos = -1; mitk::Surface* newContour = contourInfo.Contour; for (size_t i = 0; i < currentContourList.size(); i++) { auto& contourFromList = currentContourList.at(i); bool contoursAreCoplanar = ContoursCoplanar(contourInfo, contourFromList); bool contoursHaveSameLabel = contourInfo.LabelValue == contourFromList.LabelValue; // Coplanar contours have the same "pos". if (contoursAreCoplanar) { pos = contourFromList.Pos; if (contoursHaveSameLabel) { replacementIndex = i; } } } // The current contour has the same label and position as the current slice and a replacement is done. if (replacementIndex != -1) { contourInfo.Pos = pos; m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).at(replacementIndex) = contourInfo; if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } return; } // Case that there is no contour in the current slice with the current label if (pos == -1) pos = m_ContourPosIndex++; m_ContourIndex++; contourInfo.Pos = pos; m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).push_back(contourInfo); if (contourInfo.Plane == nullptr) { MITK_ERROR << "contourInfo plane is null."; } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } if (newContour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); if (m_ContourIndex > 0) m_ContourIndex--; if (m_ContourIndex > 0) m_ContourIndex--; } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) { if (!m_SelectedSegmentation) { return false; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return false; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); unsigned int currentLayerID = 0; try { currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); } catch (const std::exception& e) { MITK_ERROR << e.what() << '\n'; } auto it = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).begin(); while (it != m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).end()) { const ContourPositionInformation ¤tContour = (*it); if (ContoursCoplanar(currentContour, contourInfo)) { m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).erase(it); this->ReinitializeInterpolation(); return true; } ++it; } return false; } const mitk::Surface *mitk::SurfaceInterpolationController::GetContour(const ContourPositionInformation &contourInfo) { if (!m_SelectedSegmentation) { return nullptr; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return nullptr; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); const auto activeLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); const auto &contourList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(activeLayerID); for (auto ¤tContour : contourList) { if (ContoursCoplanar(contourInfo, currentContour)) { return currentContour.Contour; } } return nullptr; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfContours() { if (!m_SelectedSegmentation) { return -1; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return -1; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); auto contourDoubleList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep); unsigned int numContours = 0; for (auto& contourList : contourDoubleList) { numContours += contourList.size(); } return numContours; } void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) { this->ReinitializeInterpolation(); if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); unsigned int currentLayerID = 0; try { currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); } catch (const std::exception& e) { MITK_ERROR << e.what() << '\n'; } ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); if (currentImageContours.size() <= currentTimeStep) { MITK_INFO << "Contours for current time step don't exist."; return; } ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); if (currentTimeStepContoursList.size() <= currentLayerID) { MITK_INFO << "Contours for current layer don't exist."; return; } ContourPositionInformationList ¤tContours = currentTimeStepContoursList.at(currentLayerID); for (size_t i = 0; i < currentContours.size(); ++i) { if (currentContours.at(i).LabelValue == activeLabel) { m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).push_back(currentContours.at(i)); m_ReduceFilter->SetInput(m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).size()-1, currentContours.at(i).Contour); } } } void mitk::SurfaceInterpolationController::Interpolate() { if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; m_InterpolationResult = nullptr; return; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); m_ReduceFilter->Update(); m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } // We use the timeSelector to get the segmentation image for the current segmentation. mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) { mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); m_NormalsFilter->SetInput(i, reducedContour); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } if (m_CurrentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result MITK_INFO << "Interpolation impossible: not enough contours."; m_InterpolationResult = nullptr; return; } // Setting up progress bar mitk::ProgressBar::GetInstance()->AddStepsToDo(10); // create a surface from the distance-image mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(1); imageToSurfaceFilter->Update(); mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); interpolationResult->Expand(m_SelectedSegmentation->GetTimeSteps()); auto geometry = m_SelectedSegmentation->GetTimeGeometry()->Clone(); geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); interpolationResult->SetTimeGeometry(geometry); interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); m_InterpolationResult = interpolationResult; m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); auto* contoursGeometry = static_cast(m_Contours->GetTimeGeometry()); auto timeBounds = geometry->GetTimeBounds(currentTimeStep); contoursGeometry->SetFirstTimePoint(timeBounds[0]); contoursGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } mitk::Surface *mitk::SurfaceInterpolationController::GetContoursAsSurface() { return m_Contours; } void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) { m_ReduceFilter->SetMinSpacing(minSpacing); } void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) { m_ReduceFilter->SetMaxSpacing(maxSpacing); m_NormalsFilter->SetMaxSpacing(maxSpacing); } void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); } mitk::Image::Pointer mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation; } mitk::Image *mitk::SurfaceInterpolationController::GetImage() { return m_InterpolateSurfaceFilter->GetOutput(); } double mitk::SurfaceInterpolationController::EstimatePortionOfNeededMemory() { double numberOfPointsAfterReduction = m_ReduceFilter->GetNumberOfPointsAfterReduction() * 3; double sizeOfPoints = pow(numberOfPointsAfterReduction, 2) * sizeof(double); double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); double percentage = sizeOfPoints / totalMem; return percentage; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return m_ListOfInterpolationSessions.size(); } template void mitk::SurfaceInterpolationController::GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation) { this->SetCurrentInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage) { if (currentSegmentationImage.GetPointer() == m_SelectedSegmentation) { return; } if (currentSegmentationImage.IsNull()) { m_SelectedSegmentation = nullptr; return; } m_SelectedSegmentation = currentSegmentationImage.GetPointer(); try { auto labelSetImage = dynamic_cast(m_SelectedSegmentation); auto it = m_ListOfContours.find(currentSegmentationImage.GetPointer()); // If the session does not exist yet create a new ContourPositionPairList otherwise reinitialize the interpolation // pipeline if (it == m_ListOfContours.end()) { ContourPositionInformationVec3D newList; auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); for (size_t t = 0; t < numTimeSteps; ++t) { auto twoDList = ContourPositionInformationVec2D(); auto contourList = ContourPositionInformationList(); twoDList.push_back(contourList); newList.push_back(twoDList); } m_ListOfContours[m_SelectedSegmentation] = newList; m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); m_SegmentationObserverTags[m_SelectedSegmentation] = labelSetImage->AddObserver(itk::DeleteEvent(), command); m_NumberOfLayersInCurrentSegmentation = labelSetImage->GetNumberOfLayers(); } // auto labelSetImage = dynamic_cast(m_SelectedSegmentation); auto numLayersInSelectedSegmentation = labelSetImage->GetNumberOfLayers(); // Maybe this has to change. for (size_t layerID = 0; layerID < numLayersInSelectedSegmentation; ++layerID) { this->AddLabelSetConnection(layerID); } } catch (const std::exception &e) { MITK_ERROR << "Unable to cast image as LabelSetImage"; } auto it2 = m_ListOfInterpolationSessions.find(currentSegmentationImage.GetPointer()); if (it2 == m_ListOfInterpolationSessions.end()) { ContourPositionInformationVec2D newList; m_ListOfInterpolationSessions[m_SelectedSegmentation] = newList; m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } this->ReinitializeInterpolation(); } bool mitk::SurfaceInterpolationController::ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession) { if (oldSession.IsNull() || newSession.IsNull()) return false; if (oldSession.GetPointer() == newSession.GetPointer()) return false; if (!mitk::Equal(*(oldSession->GetGeometry()), *(newSession->GetGeometry()), mitk::eps, false)) return false; auto it = m_ListOfInterpolationSessions.find(oldSession.GetPointer()); if (it == m_ListOfInterpolationSessions.end()) return false; if (!newSession->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "Interpolation session cannot be replaced. Currently selected timepoint is not in the time bounds of the new session. Time point: " << m_CurrentTimePoint; return false; } ContourPositionInformationVec2D oldList = (*it).second; m_ListOfInterpolationSessions[newSession.GetPointer()] = oldList; itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); m_SegmentationObserverTags[newSession] = newSession->AddObserver(itk::DeleteEvent(), command); if (m_SelectedSegmentation == oldSession) m_SelectedSegmentation = newSession; const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); this->RemoveInterpolationSession(oldSession); return true; } void mitk::SurfaceInterpolationController::RemoveSegmentationFromContourList(mitk::Image *segmentation) { this->RemoveInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::Image::Pointer segmentationImage) { if (segmentationImage) { if (m_SelectedSegmentation == segmentationImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_ListOfInterpolationSessions.erase(segmentationImage); m_ListOfContours.erase(segmentationImage); // Remove observer auto pos = m_SegmentationObserverTags.find(segmentationImage); if (pos != m_SegmentationObserverTags.end()) { segmentationImage->RemoveObserver((*pos).second); m_SegmentationObserverTags.erase(pos); } } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); while (dataIter != m_SegmentationObserverTags.end()) { mitk::Image *image = (*dataIter).first; image->RemoveObserver((*dataIter).second); ++dataIter; } m_SegmentationObserverTags.clear(); m_SelectedSegmentation = nullptr; m_ListOfInterpolationSessions.clear(); m_ListOfContours.clear(); } template std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) { mitk::ImagePixelReadAccessor readAccessor(labelSetImage); std::vector pixelsPresent; std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { mitk::Label::PixelType pixelVal = *(src + i); if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != 0) ) { pixelsPresent.push_back(pixelVal); } } return pixelsPresent; } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, unsigned int timeStep, unsigned int layerID) { auto isContourEqualToLabelValue = [label] (ContourPositionInformation& contour) -> bool { return (contour.LabelValue == label); }; ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); ContourPositionInformationList ¤tContourList = currentImageContours.at(timeStep).at(layerID); unsigned int numContoursBefore = currentContourList.size(); auto it = std::remove_if(currentContourList.begin(), currentContourList.end(), isContourEqualToLabelValue); currentContourList.erase(it, currentContourList.end()); unsigned int numContoursAfter = currentContourList.size(); unsigned int numContours = numContoursAfter - numContoursBefore; m_ContourIndex -= numContours; } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto *tempImage = dynamic_cast(const_cast(caller)); if (tempImage) { if (m_SelectedSegmentation == tempImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_SegmentationObserverTags.erase(tempImage); m_ListOfContours.erase(tempImage); m_ListOfInterpolationSessions.erase(tempImage); } } void mitk::SurfaceInterpolationController::ReinitializeInterpolation() { // If session has changed reset the pipeline m_ReduceFilter->Reset(); m_NormalsFilter->Reset(); m_InterpolateSurfaceFilter->Reset(); // Empty out the listOfInterpolationSessions m_ListOfInterpolationSessions[m_SelectedSegmentation].clear(); itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); if (m_SelectedSegmentation) { if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; return; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); // Set reference image for interpolation surface filter mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); // Resize listofinterpolationsessions and listofcontours to numTimeSteps unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); unsigned int size = m_ListOfInterpolationSessions[m_SelectedSegmentation].size(); if (size != numTimeSteps) { m_ListOfInterpolationSessions.at(m_SelectedSegmentation).resize(numTimeSteps); } } } void mitk::SurfaceInterpolationController::AddLabelSetConnection(unsigned int layerID) { if (m_SelectedSegmentation != nullptr) { try { auto workingImage = dynamic_cast(m_SelectedSegmentation); auto previousLayerID = workingImage->GetActiveLayer(); workingImage->SetActiveLayer(layerID); auto activeLabelSet = workingImage->GetLabelSet(layerID); + + if (activeLabelSet == nullptr) + return; + activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnRemoveLabel); activeLabelSet->ActiveLabelEvent += mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnActiveLabel); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &mitk::SurfaceInterpolationController::OnLayerChanged); m_NumberOfConnectionsAdded += 1; workingImage->SetActiveLayer(previousLayerID); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void mitk::SurfaceInterpolationController::AddLabelSetConnection() { if (m_SelectedSegmentation != nullptr) { try { auto workingImage = dynamic_cast(m_SelectedSegmentation); auto activeLabelSet = workingImage->GetActiveLabelSet(); + + if (activeLabelSet == nullptr) + return; + activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnActiveLabel); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &mitk::SurfaceInterpolationController::OnLayerChanged); m_NumberOfConnectionsAdded += 1; } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void mitk::SurfaceInterpolationController::RemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) { labelSetImage->SetActiveLayer(layerID); labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnRemoveLabel); // labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( // this, &mitk::SurfaceInterpolationController::OnActiveLabel); labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &mitk::SurfaceInterpolationController::OnLayerChanged); m_NumberOfConnectionsAdded -= 1; } void mitk::SurfaceInterpolationController::RemoveLabelSetConnection() { if (m_SelectedSegmentation != nullptr) { try { auto workingImage = dynamic_cast(m_SelectedSegmentation); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( this, &mitk::SurfaceInterpolationController::OnActiveLabel); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &mitk::SurfaceInterpolationController::OnLayerChanged); } catch (const std::exception& e) { std::cerr << e.what() << '\n'; } } } void mitk::SurfaceInterpolationController::OnRemoveLabel(mitk::Label::PixelType /*removedLabelValue*/) { if (m_SelectedSegmentation != nullptr) { auto numTimeSteps = m_SelectedSegmentation->GetTimeGeometry()->CountTimeSteps(); try { auto labelSetImage = dynamic_cast(m_SelectedSegmentation); auto currentLayerID = labelSetImage->GetActiveLayer(); for(unsigned int t = 0; t < numTimeSteps; ++t) { this->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); } } catch(const std::exception& e) { std::cerr << e.what() << '\n'; } } } void mitk::SurfaceInterpolationController::OnActiveLabel(mitk::Label::PixelType newActiveLabelValue) { m_PreviousActiveLabelValue = m_CurrentActiveLabelValue; m_CurrentActiveLabelValue = newActiveLabelValue; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfLayersInCurrentSegmentation() const { return m_NumberOfLayersInCurrentSegmentation; } void mitk::SurfaceInterpolationController::SetNumberOfLayersInCurrentSegmentation(unsigned int numLayers) { m_NumberOfLayersInCurrentSegmentation = numLayers; } void mitk::SurfaceInterpolationController::OnAddLayer() { assert(m_SelectedSegmentation != nullptr); auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); // Push an information list for each time step. for(size_t t = 0; t < contoursForSegmentation.size(); ++t) { contoursForSegmentation.at(t).push_back( ContourPositionInformationList() ); } } void mitk::SurfaceInterpolationController::OnRemoveLayer() { assert(m_SelectedSegmentation != nullptr); auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); // Erase the layers in each of the time steps. // The previous layer is removed for (size_t t = 0; t < contoursForSegmentation.size(); ++t) { assert(m_PreviousLayerIndex < contoursForSegmentation.at(t).size()); auto& contoursAtTimeStep = contoursForSegmentation.at(t); for (size_t c = m_CurrentLayerIndex+1; c < contoursAtTimeStep.size(); ++c) { auto& contoursInCurrentLayer = contoursAtTimeStep.at(c); for (auto& contour : contoursInCurrentLayer) { contour.LayerValue = contour.LayerValue - 1; } } } for (size_t t = 0; t < contoursForSegmentation.size(); ++t) { assert (m_CurrentLayerIndex < contoursForSegmentation.at(t).size()); contoursForSegmentation.at(t).erase(contoursForSegmentation.at(t).begin() + m_PreviousLayerIndex); } - this->Modified(); } void mitk::SurfaceInterpolationController::OnLayerChanged() { auto currentLayer = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); m_PreviousLayerIndex = m_CurrentLayerIndex; m_CurrentLayerIndex = currentLayer; } mitk::SurfaceInterpolationController::ContourPositionInformationList* mitk::SurfaceInterpolationController::GetContours(unsigned int timeStep, unsigned int layerID) { if (m_SelectedSegmentation == nullptr) return nullptr; if (timeStep >= m_ListOfContours.at(m_SelectedSegmentation).size()) return nullptr; if (layerID >= m_ListOfContours.at(m_SelectedSegmentation).at(timeStep).size()) return nullptr; return &m_ListOfContours[m_SelectedSegmentation][timeStep][layerID]; } void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector& contourList, std::vector& contourPlanes) { this->ClearInterpolationSession(); auto labelSetImage = dynamic_cast(m_SelectedSegmentation); auto numLayers = labelSetImage->GetNumberOfLayers(); // Add layers to the m_ListOfContours for (size_t layer = 0; layer < numLayers; ++layer) { this->OnAddLayer(); } // Now the layers should be empty and the new layers can be added. this->AddNewContours(contourList, contourPlanes, true); } void mitk::SurfaceInterpolationController::ClearInterpolationSession() { if (m_SelectedSegmentation != nullptr) { auto it = m_ListOfContours.find(m_SelectedSegmentation); if (it != m_ListOfContours.end()) { auto timeSteps = m_ListOfContours[m_SelectedSegmentation].size(); try { auto labelSetImage = dynamic_cast(m_SelectedSegmentation); auto labelSetImageTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); if (timeSteps != labelSetImageTimeSteps) { MITK_ERROR << "Time steps are not the same."; } for (size_t t = 0; t < timeSteps; ++t) { m_ListOfContours[m_SelectedSegmentation][t].clear(); } } catch(std::bad_cast& e) { MITK_ERROR << "Unable to cast m_SelectedSegmentation to labelSetImage in ClearInterpolationSession"; } } } } std::vector< mitk::Point3D > mitk::ContourExt::GetBoundingBoxGridPoints( size_t planeDimension, double startDim1, size_t numPointsToSampleDim1, double deltaDim1, double startDim2, size_t numPointsToSampleDim2, double deltaDim2, double valuePlaneDim) { std::vector< mitk::Point3D > gridPoints; for (size_t i = 0; i < numPointsToSampleDim1; ++i) { for (size_t j = 0; j < numPointsToSampleDim2; ++j) { mitk::ScalarType *ptVec = new mitk::ScalarType[3]; if (planeDimension == 0) { ptVec[0] = valuePlaneDim; ptVec[1] = startDim1 + deltaDim1 * i; ptVec[2] = startDim2 + deltaDim2 * j; } else if (planeDimension == 1) { ptVec[0] = startDim1 + deltaDim1 * i; ptVec[1] = valuePlaneDim; ptVec[2] = startDim2 + deltaDim2 * j; } else if (planeDimension == 2) { ptVec[0] = startDim1 + deltaDim1 * i; ptVec[1] = startDim2 + deltaDim2 * j; ptVec[2] = valuePlaneDim; } mitk::Point3D pt3D; pt3D.FillPoint(ptVec); gridPoints.push_back(pt3D); } } return gridPoints; } mitk::Point3D mitk::SurfaceInterpolationController::ComputeInteriorPointOfContour( const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, mitk::LabelSetImage * labelSetImage) { if (labelSetImage->GetDimension() == 4) { return mitk::ContourExt::ComputeInteriorPointOfContour<4>(contour, labelSetImage, m_CurrentTimePoint); } else { return mitk::ContourExt::ComputeInteriorPointOfContour<3>(contour, labelSetImage, m_CurrentTimePoint); } } template mitk::Point3D mitk::ContourExt::ComputeInteriorPointOfContour( const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, mitk::LabelSetImage * labelSetImage, mitk::TimePointType currentTimePoint) { mitk::ImagePixelReadAccessor readAccessor(labelSetImage); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; mitk::Point3D pt; return pt; } std::vector pixelsPresent; const auto currentTimeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); auto polyData = contour.Contour->GetVtkPolyData(); polyData->ComputeCellsBounds(); mitk::ScalarType cellBounds[6]; polyData->GetCellsBounds(cellBounds); size_t numPointsToSample = 10; mitk::ScalarType StartX = cellBounds[0]; mitk::ScalarType StartY = cellBounds[2]; mitk::ScalarType StartZ = cellBounds[4]; size_t deltaX = (cellBounds[1] - cellBounds[0]) / numPointsToSample; size_t deltaY = (cellBounds[3] - cellBounds[2]) / numPointsToSample; size_t deltaZ = (cellBounds[5] - cellBounds[4]) / numPointsToSample; auto planeOrientation = mitk::ContourExt::GetContourOrientation(contour.ContourNormal); std::vector points; if (planeOrientation == 0) { points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, StartY, numPointsToSample, deltaY, StartZ, numPointsToSample, deltaZ, StartX); } else if (planeOrientation == 1) { points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, StartX, numPointsToSample, deltaX, StartZ, numPointsToSample, deltaZ, StartY); } else if (planeOrientation == 2) { points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, StartX, numPointsToSample, deltaX, StartY, numPointsToSample, deltaY, StartZ); } mitk::Label::PixelType pixelVal; mitk::Point3D pt3D; std::vector pixelVals; for (size_t i = 0; i < points.size(); ++i) { pt3D = points[i]; itk::Index<3> itkIndex; labelSetImage->GetGeometry()->WorldToIndex(pt3D, itkIndex); if (VImageDimension == 4) { itk::Index time3DIndex; for (size_t i = 0; i < itkIndex.size(); ++i) time3DIndex[i] = itkIndex[i]; time3DIndex[3] = currentTimeStep; pixelVal = readAccessor.GetPixelByIndexSafe(time3DIndex); } else if (VImageDimension == 3) { itk::Index geomIndex; for (size_t i=0;i mitk::eps) { planeOrientation = 2; } else if (fabs(dotY) > mitk::eps) { planeOrientation = 1; } else if(fabs(dotX) > mitk::eps) { planeOrientation = 0; } return planeOrientation; } diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index 8b336a0d7e..abae09f22c 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,89 +1,88 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:ON org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF org.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON - org.mitk.core.ext:OFF org.mitk.core.jobs:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.mitkworkbench.intro:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.mxnmultiwidgeteditor:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.chartExample:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.datastorageviewertest:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicombrowser:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.deformableclippingplane:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.renderwindowmanager:OFF org.mitk.gui.qt.semanticrelations:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation:OFF org.mitk.gui.qt.classificationsegmentation:OFF org.mitk.gui.qt.overlaymanager:OFF org.mitk.gui.qt.igt.app.hummelprotocolmeasurements:OFF org.mitk.matchpoint.core.helper:OFF org.mitk.gui.qt.matchpoint.algorithm.browser:OFF org.mitk.gui.qt.matchpoint.algorithm.control:OFF org.mitk.gui.qt.matchpoint.mapper:OFF org.mitk.gui.qt.matchpoint.framereg:OFF org.mitk.gui.qt.matchpoint.visualizer:OFF org.mitk.gui.qt.matchpoint.evaluator:OFF org.mitk.gui.qt.matchpoint.manipulator:OFF org.mitk.gui.qt.preprocessing.resampling:OFF org.mitk.gui.qt.radiomics:OFF org.mitk.gui.qt.cest:OFF org.mitk.gui.qt.fit.demo:OFF org.mitk.gui.qt.fit.inspector:OFF org.mitk.gui.qt.fit.genericfitting:OFF org.mitk.gui.qt.pharmacokinetics.mri:OFF org.mitk.gui.qt.pharmacokinetics.pet:OFF org.mitk.gui.qt.pharmacokinetics.simulation:OFF org.mitk.gui.qt.pharmacokinetics.curvedescriptor:OFF org.mitk.gui.qt.pharmacokinetics.concentration.mri:OFF org.mitk.gui.qt.flowapplication:OFF org.mitk.gui.qt.flow.segmentation:OFF org.mitk.gui.qt.pixelvalue:ON ) diff --git a/Plugins/org.mitk.core.ext/CMakeLists.txt b/Plugins/org.mitk.core.ext/CMakeLists.txt deleted file mode 100755 index 1eeffccb13..0000000000 --- a/Plugins/org.mitk.core.ext/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -project(org_mitk_core_ext) - -mitk_create_plugin( - EXPORT_DIRECTIVE MITKCOREEXT_EXPORT - EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkCore -) - diff --git a/Plugins/org.mitk.core.ext/documentation/doxygen/modules.dox b/Plugins/org.mitk.core.ext/documentation/doxygen/modules.dox deleted file mode 100755 index 746060c9f1..0000000000 --- a/Plugins/org.mitk.core.ext/documentation/doxygen/modules.dox +++ /dev/null @@ -1,19 +0,0 @@ -/** - \defgroup org_mitk_core_ext org.mitk.core.ext - \ingroup MITKPlugins - - \brief This small plug-in is responsible for initializing the MBI CoreExt module. - - It is started with an "eager" Bundle-ActivationPolicy, sucht that the plug-ins - activator is executed before any other (non-eager) plug-ins. - -*/ - -/** - \defgroup org_mitk_core_ext_internal Internal - \ingroup org_mitk_core_ext - - \brief This subcategory includes the internal classes of the org.mitk.core.ext plugin. Other - plugins must not rely on these classes. They contain implementation details and their interface - may change at any time. We mean it. -*/ diff --git a/Plugins/org.mitk.core.ext/files.cmake b/Plugins/org.mitk.core.ext/files.cmake deleted file mode 100755 index d16543df69..0000000000 --- a/Plugins/org.mitk.core.ext/files.cmake +++ /dev/null @@ -1,33 +0,0 @@ - -set(SRC_CPP_FILES - mitkCoreExtConstants.cpp - mitkIInputDeviceDescriptor.h - mitkIInputDeviceRegistry.h - mitkIInputDevice.h -) - -set(INTERNAL_CPP_FILES - mitkCoreExtActivator.cpp - mitkInputDeviceRegistry.cpp - mitkInputDeviceDescriptor.cpp -) - -set(CACHED_RESOURCE_FILES - plugin.xml -) - -set(MOC_H_FILES - src/internal/mitkCoreExtActivator.h - src/internal/mitkInputDeviceRegistry.h -) - -set(CPP_FILES ) - -foreach(file ${SRC_CPP_FILES}) - set(CPP_FILES ${CPP_FILES} src/${file}) -endforeach(file ${SRC_CPP_FILES}) - -foreach(file ${INTERNAL_CPP_FILES}) - set(CPP_FILES ${CPP_FILES} src/internal/${file}) -endforeach(file ${INTERNAL_CPP_FILES}) - diff --git a/Plugins/org.mitk.core.ext/manifest_headers.cmake b/Plugins/org.mitk.core.ext/manifest_headers.cmake deleted file mode 100644 index 32a48a5359..0000000000 --- a/Plugins/org.mitk.core.ext/manifest_headers.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(Plugin-Name "MITK Extensions") -set(Plugin-Version "0.1") -set(Plugin-Vendor "German Cancer Research Center (DKFZ)") -set(Plugin-ContactAddress "https://www.mitk.org") -set(Require-Plugin org.blueberry.core.runtime) diff --git a/Plugins/org.mitk.core.ext/plugin.xml b/Plugins/org.mitk.core.ext/plugin.xml deleted file mode 100644 index 6b6ceeb06f..0000000000 --- a/Plugins/org.mitk.core.ext/plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/Plugins/org.mitk.core.ext/schema/inputdevice.exsd b/Plugins/org.mitk.core.ext/schema/inputdevice.exsd deleted file mode 100644 index cc2159a250..0000000000 --- a/Plugins/org.mitk.core.ext/schema/inputdevice.exsd +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.cpp b/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.cpp deleted file mode 100644 index 6f00d0b756..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkCoreExtActivator.h" - -#include "mitkCoreExtConstants.h" -#include "mitkLogMacros.h" - -#include - -#include -#include -#include - -#include - -US_INITIALIZE_MODULE - -namespace mitk -{ - void CoreExtActivator::start(ctkPluginContext* context) - { - Q_UNUSED(context) - - this->StartInputDeviceModules(context); - } - - void CoreExtActivator::stop(ctkPluginContext* context) - { - Q_UNUSED(context) - } - - void CoreExtActivator::StartInputDeviceModules(ctkPluginContext* context) - { - m_InputDeviceRegistry.reset(new InputDeviceRegistry()); - context->registerService(m_InputDeviceRegistry.data()); - - auto* prefService = mitk::CoreServices::GetPreferencesService(); - auto* extPreferencesNode = prefService->GetSystemPreferences()->Node(CoreExtConstants::INPUTDEVICE_PREFERENCES.toStdString()); - - // Initializes the modules - QList descriptors(m_InputDeviceRegistry->GetInputDevices()); - for (QList::const_iterator it = descriptors.begin(); - it != descriptors.end(); ++it) - { - if (extPreferencesNode->GetBool((*it)->GetID().toStdString(), false)) - { - IInputDevice::Pointer temp = (*it)->CreateInputDevice(); - temp->RegisterInputDevice(); - } - } - } - - CoreExtActivator::~CoreExtActivator() - { - } - -} // end namespace mitk diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.h b/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.h deleted file mode 100755 index 42a15fea33..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkCoreExtActivator.h +++ /dev/null @@ -1,64 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef mitkCoreExtActivator_h -#define mitkCoreExtActivator_h - -#include - -#include "mitkInputDeviceRegistry.h" - -namespace mitk -{ - - /** - * @brief The activator class for the org.mitk.core.ext plug-in. - * @ingroup org_mitk_core_ext_internal - * - * When the plug-in is started by the framework, it calls a global function to initialize - * the mitkCoreExt module. - * - */ - class CoreExtActivator : public QObject, public ctkPluginActivator - { - Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_core_ext") - Q_INTERFACES(ctkPluginActivator) - - public: - - ~CoreExtActivator() override; - - /** - * Starts this plug-in and registers object factories. - * - * @param context - * The context for the plug-in. - */ - void start(ctkPluginContext* context) override; - - void stop(ctkPluginContext* context) override; - - private: - - /** - * Activates the input device modules. - */ - void StartInputDeviceModules(ctkPluginContext *context); - - QScopedPointer m_InputDeviceRegistry; - - }; // end class CoreExtActivator -} //end namespace mitk - -#endif diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.cpp b/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.cpp deleted file mode 100644 index d4c056a44a..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkInputDeviceDescriptor.h" -#include "mitkCoreExtConstants.h" - - -mitk::InputDeviceDescriptor::InputDeviceDescriptor(berry::IConfigurationElement::Pointer inputDeviceExtensionPoint) -: IInputDeviceDescriptor(), m_InputDeviceExtensionPoint(inputDeviceExtensionPoint) -{ -} - -mitk::InputDeviceDescriptor::~InputDeviceDescriptor() -{ -} - -mitk::IInputDevice::Pointer mitk::InputDeviceDescriptor::CreateInputDevice() -{ - if(this->m_InputDevice == 0) - { - // "class" refers to xml attribute in a xml tag - this->m_InputDevice = this->m_InputDeviceExtensionPoint - ->CreateExecutableExtension(mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_CLASS); - if (this->m_InputDevice == 0) - { - // support legacy BlueBerry extensions - this->m_InputDevice = this->m_InputDeviceExtensionPoint - ->CreateExecutableExtension(mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_CLASS); - } - } - return this->m_InputDevice; -} - -QString mitk::InputDeviceDescriptor::GetID() const -{ - return this->m_InputDeviceExtensionPoint->GetAttribute(mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_ID); -} - -QString mitk::InputDeviceDescriptor::GetDescription() const -{ - QList - descriptions(this->m_InputDeviceExtensionPoint->GetChildren(mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_DESCRIPTION)); - - if(!descriptions.empty()) - { - return descriptions[0]->GetValue(); - } - return QString(); -} - -QString mitk::InputDeviceDescriptor::GetName() const -{ - return this->m_InputDeviceExtensionPoint->GetAttribute(mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_NAME); -} - -bool mitk::InputDeviceDescriptor::operator==(const Object* object) const -{ - if (const InputDeviceDescriptor* other = dynamic_cast(object)) - { - return this->GetID() == other->GetID(); - } - return false; -} - diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.h b/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.h deleted file mode 100644 index d04d51aee0..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceDescriptor.h +++ /dev/null @@ -1,84 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkInputDeviceDescriptor_h -#define mitkInputDeviceDescriptor_h - -#include - -#include - -#include "mitkIInputDeviceDescriptor.h" -#include "mitkIInputDevice.h" - -namespace mitk -{ - -/** - * Documentation in the interface. - * - * @see mitk::IInputDeviceDescriptor - * @ingroup org_mitk_core_ext - */ -class InputDeviceDescriptor : public IInputDeviceDescriptor -{ - -public: - - /** - * Initialize the Input Device Descriptor with the given extension point. - * - * @param inputDeviceExtensionPoint - * element, that refers to a extension point (type, id, name, class) - */ - InputDeviceDescriptor(berry::IConfigurationElement::Pointer inputDeviceExtensionPoint); - - /** - * Default destructor - */ - ~InputDeviceDescriptor() override; - - /** - * @see mitk::IInputDeviceDescriptor::CreateInputDevice() - */ - mitk::IInputDevice::Pointer CreateInputDevice() override; - - /** - * @see mitk::IInputDeviceDescriptor::GetDescription() - */ - QString GetDescription() const override; - - /** - * @see mitk::IInputDeviceDescriptor::GetID() - */ - QString GetID() const override; - - /** - * @see mitk::IInputDeviceDescriptor::GetName() - */ - QString GetName() const override; - - /** - * @see mitk::IInputDeviceDescriptor::operator==(const Object* object) - */ - bool operator==(const Object* object) const override; - -private: - - // IConfigurationElements are used to access xml files (here: plugin.xml) - berry::IConfigurationElement::Pointer m_InputDeviceExtensionPoint; - mitk::IInputDevice::Pointer m_InputDevice; - -}; // end class -} // end namespace - -#endif diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.cpp b/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.cpp deleted file mode 100644 index c4dd08483d..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include -#include -#include - -#include - -#include "mitkInputDeviceRegistry.h" -#include "mitkInputDeviceDescriptor.h" - - -mitk::InputDeviceRegistry::InputDeviceRegistry() -{ - //initialize the registry by copying all available extension points into a local variable - berry::IExtensionRegistry* extensionPointService = berry::Platform::GetExtensionRegistry(); - QList allExtensionsInputDevices - = extensionPointService->GetConfigurationElementsFor(mitk::CoreExtConstants::INPUTDEVICE_EXTENSION_NAME); - - for(QList::const_iterator it = allExtensionsInputDevices.begin(); - it != allExtensionsInputDevices.end();++it) - { - InputDeviceDescriptorPtr temp(new mitk::InputDeviceDescriptor(*it)); - - // The equation with the end means, that if there is no such element and - // the pointer will be at end (not the last element, actually after it) - if(this->m_ListRegisteredDevices.find(temp->GetID()) == this->m_ListRegisteredDevices.end()) - { - m_ListRegisteredDevices.insert(temp->GetID(), temp); - } - else - { - throw ctkRuntimeException("The Input Device ID: " + temp->GetID() + " is already registered."); - } - } -} - -mitk::InputDeviceRegistry::~InputDeviceRegistry() -{ -} - -mitk::InputDeviceRegistry::InputDeviceDescriptorPtr mitk::InputDeviceRegistry::Find(const QString &id) const -{ - QHash::ConstIterator result = this->m_ListRegisteredDevices.find(id); - - // first = key, second = element or vice versa, if inserted different in the hash map - if(result != this->m_ListRegisteredDevices.end()) return result.value(); - - return InputDeviceDescriptorPtr(nullptr); -} - -QList mitk::InputDeviceRegistry::GetInputDevices() const -{ - return m_ListRegisteredDevices.values(); -} diff --git a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.h b/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.h deleted file mode 100644 index 459c1f5e3a..0000000000 --- a/Plugins/org.mitk.core.ext/src/internal/mitkInputDeviceRegistry.h +++ /dev/null @@ -1,60 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkInputDeviceRegistry_h -#define mitkInputDeviceRegistry_h - -#include -#include - -#include - -namespace mitk -{ - /** - * Documentation in the interface. - * - * @see mitk::IInputDeviceRegistry - * @ingroup org_mitk_core_ext - */ - class InputDeviceRegistry : public QObject, public IInputDeviceRegistry - { - Q_OBJECT - Q_INTERFACES(mitk::IInputDeviceRegistry) - - public: - - // easier maintenance - typedef IInputDeviceDescriptor::Pointer InputDeviceDescriptorPtr; - - InputDeviceRegistry(); - ~InputDeviceRegistry() override; - - /** - * @see mitk::IInputDeviceRegistry::Find(const std::string& id) - */ - InputDeviceDescriptorPtr Find(const QString& id) const override; - - /** - * @see mitk::IInputDeviceRegistry´::GetInputDevices() - */ - QList GetInputDevices() const override; - - protected: - - private: - QHash m_ListRegisteredDevices; - - }; // end class InputDeviceRegistry -} // end namespace mitk - -#endif diff --git a/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.cpp b/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.cpp deleted file mode 100644 index b88cd9ec1b..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include - -const QString mitk::CoreExtConstants::INPUTDEVICE_SERVICE = "org.mitk.core.ext.services.inputdeviceregistry"; -const QString mitk::CoreExtConstants::INPUTDEVICE_PREFERENCES = "org.mitk.core.ext.preferences.inputdevices"; -const QString mitk::CoreExtConstants::INPUTDEVICE_EXTENSION_NAME = "org.mitk.core.ext.inputdevices"; -const QString mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_CLASS = "class"; -const QString mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_DESCRIPTION = "description"; -const QString mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_NAME = "name"; -const QString mitk::CoreExtConstants::INPUTDEVICE_XMLATTRIBUTE_ID = "id"; -const QString mitk::CoreExtConstants::WIIMOTE_SURFACEINTERACTION = "Surface Interaction"; -const QString mitk::CoreExtConstants::WIIMOTE_HEADTRACKING = "Headtracking"; -const QString mitk::CoreExtConstants::WIIMOTE_XMLATTRIBUTE_NAME = "org.mitk.inputdevices.wiimote"; diff --git a/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.h b/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.h deleted file mode 100644 index 4971b71e7b..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkCoreExtConstants.h +++ /dev/null @@ -1,49 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkCoreExtConstants_h -#define mitkCoreExtConstants_h - -#include - -#include - -namespace mitk -{ - /** - * The CoreExt Constants contains a list of unique ids in the following form:
- * "org.mitk.mybundle.mytype.propername"
- * - * This ids have the purpose of connecting the plugin.xml of each bundle to the - * appropriate classes. - * - * Additionally it includes names of tags, which are used in XML files
- * regarding any input device. - * - * @ingroup org_mitk_core_ext - */ - struct MITKCOREEXT_EXPORT CoreExtConstants - { - static const QString INPUTDEVICE_SERVICE; - static const QString INPUTDEVICE_PREFERENCES; - static const QString INPUTDEVICE_EXTENSION_NAME; - static const QString INPUTDEVICE_XMLATTRIBUTE_CLASS; - static const QString INPUTDEVICE_XMLATTRIBUTE_DESCRIPTION; - static const QString INPUTDEVICE_XMLATTRIBUTE_NAME; - static const QString INPUTDEVICE_XMLATTRIBUTE_ID; - static const QString WIIMOTE_SURFACEINTERACTION; - static const QString WIIMOTE_HEADTRACKING; - static const QString WIIMOTE_XMLATTRIBUTE_NAME; - }; -} - -#endif diff --git a/Plugins/org.mitk.core.ext/src/mitkIInputDevice.h b/Plugins/org.mitk.core.ext/src/mitkIInputDevice.h deleted file mode 100644 index ccb40e8385..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkIInputDevice.h +++ /dev/null @@ -1,52 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkIInputDevice_h -#define mitkIInputDevice_h - -#include -#include - -#include - -namespace mitk -{ - /** - * An input device provides a method to register and unregister itself. Meaning
- * for example adding listeners and instianciate classes necessary to use the input device. - * - * @note This interface is not intended to be implemented by clients. - * @ingroup org_mitk_core_ext - */ - struct IInputDevice : public berry::Object - { - - berryObjectMacro(mitk::IInputDevice); - - /** - * Register the input device at one or more instances. - */ - virtual bool RegisterInputDevice() = 0; - - /** - * Unregister the input device at one or more instances. - */ - virtual bool UnRegisterInputDevice() = 0; - - ~IInputDevice() override {} - - }; // end struct IInputDevice -} // end namespace mitk - -Q_DECLARE_INTERFACE(mitk::IInputDevice, "org.mitk.IInputDevice") - -#endif diff --git a/Plugins/org.mitk.core.ext/src/mitkIInputDeviceDescriptor.h b/Plugins/org.mitk.core.ext/src/mitkIInputDeviceDescriptor.h deleted file mode 100644 index ab24f45834..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkIInputDeviceDescriptor.h +++ /dev/null @@ -1,86 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkIInputDeviceDescriptor_h -#define mitkIInputDeviceDescriptor_h - -#include -#include - -#include "mitkIInputDevice.h" - -namespace mitk -{ - /** - * This is an input device descriptor. It provides a "description" of a given - * input device, so that the input device can later be constructed and registered. - *

- * The input device registry provides facilities to map from an extension - * to a IInputDeviceDescriptor. - *

- *

- * This interface is not intended to be implemented by clients. - *

- * - * @see mitk::IInputDeviceRegistry - * @ingroup org_mitk_core_ext - */ - struct IInputDeviceDescriptor : public berry::Object - { - - berryObjectMacro(mitk::IInputDeviceDescriptor); - - /** - * Creates an instance of an input device defined in the descriptor. - * - * @return the input device - */ - virtual IInputDevice::Pointer CreateInputDevice() = 0; - - /** - * Returns the description of this input device. - * - * @return the description - */ - virtual QString GetDescription() const = 0; - - /** - * Returns the id of this input device. - * - * @return the id - */ - virtual QString GetID() const = 0; - - /** - * Returns the name of this input device. - * - * @return the name - */ - virtual QString GetName() const = 0; - - // /** - // * Returns the descriptor for the icon to show for this view. - // */ - //virtual SmartPointer GetImageDescriptor() const = 0; - - /** - * Equals this class with the given parameter. - * - * @param object the object for the equation - * @return true, if the objects are equal :: false, if they differ in any way - */ - bool operator==(const Object* object) const override = 0; - - }; // end struct IInputDeviceDescriptor -} //end namespace mitk - -#endif diff --git a/Plugins/org.mitk.core.ext/src/mitkIInputDeviceRegistry.h b/Plugins/org.mitk.core.ext/src/mitkIInputDeviceRegistry.h deleted file mode 100644 index 1d59a8505c..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkIInputDeviceRegistry.h +++ /dev/null @@ -1,64 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkIInputDeviceRegistry_h -#define mitkIInputDeviceRegistry_h - -#include - -#include "mitkIInputDeviceDescriptor.h" - -namespace mitk -{ - /** - * - * The input device registry maintains a list of input devices explicitly registered - * against the view extension point. - *

- * The description of a given input device is kept in a IInputDeviceDescriptor. - *

- *

- * This interface is not intended to be implemented by clients. - *

- * - * @see mitk::IInputDeviceDescriptor - * @note This interface is not intended to be implemented by clients. - * @ingroup org_mitk_core_ext - */ - struct IInputDeviceRegistry - { - - /** - * Return an input device descriptor with the given extension id. If no input device exists, - * with the id return null. - * - * @param id - * the id to search for - * @return the descriptor or null - */ - virtual IInputDeviceDescriptor::Pointer Find(const QString& id) const = 0; - - /** - * Return a list of input devices defined in the registry. - * - * @return the input devices. - */ - virtual QList GetInputDevices() const = 0; - - virtual ~IInputDeviceRegistry() {} - - }; // end struct IInputDeviceRegistry -} // end namespace mitk - -Q_DECLARE_INTERFACE(mitk::IInputDeviceRegistry, "org.mitk.service.IInputDeviceRegistry") - -#endif diff --git a/Plugins/org.mitk.core.ext/src/mitkInputDeviceDescriptor.h b/Plugins/org.mitk.core.ext/src/mitkInputDeviceDescriptor.h deleted file mode 100644 index 25e99a8bbb..0000000000 --- a/Plugins/org.mitk.core.ext/src/mitkInputDeviceDescriptor.h +++ /dev/null @@ -1,83 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkInputDeviceDescriptor_h -#define mitkInputDeviceDescriptor_h - -#include - -#include - -#include "mitkIInputDeviceDescriptor.h" -#include "mitkIInputDevice.h" - -namespace mitk -{ - /** - * Documentation in the interface. - * - * @see mitk::IInputDeviceDescriptor - * @ingroup org_mitk_core_ext - */ - class InputDeviceDescriptor : public IInputDeviceDescriptor - { - - public: - - /** - * Initialize the Input Device Descriptor with the given extension point. - * - * @param inputDeviceExtensionPoint - * element, that refers to a extension point (type, id, name, class) - */ - InputDeviceDescriptor(berry::IConfigurationElement::Pointer inputDeviceExtensionPoint); - - /** - * Default destructor - */ - ~InputDeviceDescriptor(); - - /** - * @see mitk::IInputDeviceDescriptor::CreateInputDevice() - */ - mitk::IInputDevice::Pointer CreateInputDevice(); - - /** - * @see mitk::IInputDeviceDescriptor::GetDescription() - */ - std::string GetDescription() const; - - /** - * @see mitk::IInputDeviceDescriptor::GetID() - */ - std::string GetID() const; - - /** - * @see mitk::IInputDeviceDescriptor::GetName() - */ - std::string GetName() const; - - /** - * @see mitk::IInputDeviceDescriptor::operator==(const Object* object) - */ - bool operator==(const Object* object) const; - - private: - - // IConfigurationElements are used to access xml files (here: plugin.xml) - berry::IConfigurationElement::Pointer m_InputDeviceExtensionPoint; - mitk::IInputDevice::Pointer m_InputDevice; - - }; // end class -} // end namespace - -#endif diff --git a/Plugins/org.mitk.gui.qt.ext/files.cmake b/Plugins/org.mitk.gui.qt.ext/files.cmake index 562bc5d984..d00f357e6c 100644 --- a/Plugins/org.mitk.gui.qt.ext/files.cmake +++ b/Plugins/org.mitk.gui.qt.ext/files.cmake @@ -1,63 +1,61 @@ set(SRC_CPP_FILES QmitkExtActionBarAdvisor.cpp QmitkExtWorkbenchWindowAdvisor.cpp QmitkExtFileSaveProjectAction.cpp QmitkOpenDicomEditorAction.cpp QmitkOpenMxNMultiWidgetEditorAction.cpp QmitkOpenStdMultiWidgetEditorAction.cpp ) set(INTERNAL_CPP_FILES QmitkAboutHandler.cpp QmitkAppInstancesPreferencePage.cpp QmitkExternalProgramsPreferencePage.cpp QmitkCommonExtPlugin.cpp - QmitkInputDevicesPrefPage.cpp QmitkModuleView.cpp ) set(UI_FILES src/internal/QmitkAppInstancesPreferencePage.ui src/internal/QmitkExternalProgramsPreferencePage.ui ) set(MOC_H_FILES src/QmitkExtFileSaveProjectAction.h src/QmitkExtWorkbenchWindowAdvisor.h src/internal/QmitkAboutHandler.h src/internal/QmitkAppInstancesPreferencePage.h src/internal/QmitkExternalProgramsPreferencePage.h src/internal/QmitkCommonExtPlugin.h src/internal/QmitkExtWorkbenchWindowAdvisorHack.h - src/internal/QmitkInputDevicesPrefPage.h src/internal/QmitkModuleView.h src/QmitkOpenDicomEditorAction.h src/QmitkOpenMxNMultiWidgetEditorAction.h src/QmitkOpenStdMultiWidgetEditorAction.h ) set(CACHED_RESOURCE_FILES # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench plugin.xml resources/ModuleView.png ) set(QRC_FILES # uncomment the following line if you want to use Qt resources resources/org_mitk_gui_qt_ext.qrc resources/org_mitk_icons.qrc ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.ext/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.ext/manifest_headers.cmake index 9a31004ed0..52edead2e7 100644 --- a/Plugins/org.mitk.gui.qt.ext/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.ext/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Common Qt Ext Plugin") set(Plugin-Version "0.1") set(Plugin-Vendor "German Cancer Research Center (DKFZ)") set(Plugin-ContactAddress "https://www.mitk.org") -set(Require-Plugin org.mitk.core.ext org.mitk.gui.qt.application) +set(Require-Plugin org.mitk.gui.qt.application) diff --git a/Plugins/org.mitk.gui.qt.ext/plugin.xml b/Plugins/org.mitk.gui.qt.ext/plugin.xml index ecba76ad09..a1656fa533 100644 --- a/Plugins/org.mitk.gui.qt.ext/plugin.xml +++ b/Plugins/org.mitk.gui.qt.ext/plugin.xml @@ -1,42 +1,36 @@ - - - - - - diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp index baf901c607..7e85faa6f1 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp @@ -1,239 +1,237 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkCommonExtPlugin.h" #include #include "QmitkAboutHandler.h" #include "QmitkAppInstancesPreferencePage.h" #include "QmitkExternalProgramsPreferencePage.h" -#include "QmitkInputDevicesPrefPage.h" #include "QmitkModuleView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include US_INITIALIZE_MODULE ctkPluginContext* QmitkCommonExtPlugin::_context = nullptr; void QmitkCommonExtPlugin::start(ctkPluginContext* context) { this->_context = context; QtWidgetsExtRegisterClasses(); BERRY_REGISTER_EXTENSION_CLASS(QmitkAboutHandler, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAppInstancesPreferencePage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkExternalProgramsPreferencePage, context) - BERRY_REGISTER_EXTENSION_CLASS(QmitkInputDevicesPrefPage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkModuleView, context) if (qApp->metaObject()->indexOfSignal("messageReceived(QByteArray)") > -1) { connect(qApp, SIGNAL(messageReceived(QByteArray)), this, SLOT(handleIPCMessage(QByteArray))); } // This is a potentially long running operation. loadDataFromDisk(berry::Platform::GetApplicationArgs(), true); } void QmitkCommonExtPlugin::stop(ctkPluginContext* context) { Q_UNUSED(context) this->_context = nullptr; } ctkPluginContext* QmitkCommonExtPlugin::getContext() { return _context; } void QmitkCommonExtPlugin::loadDataFromDisk(const QStringList &arguments, bool globalReinit) { if (!arguments.empty()) { ctkServiceReference serviceRef = _context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService* dataStorageService = _context->getService(serviceRef); mitk::DataStorage::Pointer dataStorage = dataStorageService->GetDefaultDataStorage()->GetDataStorage(); int argumentsAdded = 0; for (int i = 0; i < arguments.size(); ++i) { if (arguments[i].right(5) == ".mitk") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadScene( arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst ); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else if (arguments[i].right(15) == ".mitksceneindex") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadSceneUnzipped(arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else { try { const std::string path(arguments[i].toStdString()); auto addedNodes = mitk::IOUtil::Load(path, *dataStorage); for (const auto& node : *addedNodes ) { node->SetIntProperty("layer", argumentsAdded); } argumentsAdded++; } catch(...) { MITK_WARN << "Failed to load command line argument: " << arguments[i].toStdString(); } } } // end for each command line argument if (argumentsAdded > 0 && globalReinit) { // calculate bounding geometry mitk::RenderingManager::GetInstance()->InitializeViews(dataStorage->ComputeBoundingGeometry3D()); } } else { MITK_ERROR << "A service reference for mitk::IDataStorageService does not exist"; } } } void QmitkCommonExtPlugin::startNewInstance(const QStringList &args, const QStringList& files) { QStringList newArgs(args); #ifdef Q_OS_UNIX newArgs << QString("--") + mitk::BaseApplication::ARG_NEWINSTANCE; #else newArgs << QString("/") + mitk::BaseApplication::ARG_NEWINSTANCE; #endif newArgs << files; QProcess::startDetached(qApp->applicationFilePath(), newArgs); } void QmitkCommonExtPlugin::handleIPCMessage(const QByteArray& msg) { QDataStream ds(msg); QString msgType; ds >> msgType; // we only handle messages containing command line arguments if (msgType != "$cmdLineArgs") return; // activate the current workbench window berry::IWorkbenchWindow::Pointer window = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); mainWindow->setWindowState(mainWindow->windowState() & ~Qt::WindowMinimized); mainWindow->raise(); mainWindow->activateWindow(); // Get the preferences for the instantiation behavior auto* prefService = mitk::CoreServices::GetPreferencesService(); auto* prefs = prefService->GetSystemPreferences()->Node("/General"); bool newInstanceAlways = prefs->GetBool("newInstance.always", false); bool newInstanceScene = prefs->GetBool("newInstance.scene", true); QStringList args; ds >> args; QStringList fileArgs; QStringList sceneArgs; foreach (QString arg, args) { if (arg.endsWith(".mitk")) { sceneArgs << arg; } else { fileArgs << arg; } } if (newInstanceAlways) { if (newInstanceScene) { startNewInstance(args, fileArgs); foreach(QString sceneFile, sceneArgs) { startNewInstance(args, QStringList(sceneFile)); } } else { fileArgs.append(sceneArgs); startNewInstance(args, fileArgs); } } else { loadDataFromDisk(fileArgs, false); if (newInstanceScene) { foreach(QString sceneFile, sceneArgs) { startNewInstance(args, QStringList(sceneFile)); } } else { loadDataFromDisk(sceneArgs, false); } } } diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.cpp b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.cpp deleted file mode 100644 index ad4632cd6a..0000000000 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkInputDevicesPrefPage.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "QmitkCommonExtPlugin.h" - -namespace -{ - mitk::IPreferences* GetPreferences() - { - auto* preferencesService = mitk::CoreServices::GetPreferencesService(); - return preferencesService->GetSystemPreferences()->Node(mitk::CoreExtConstants::INPUTDEVICE_PREFERENCES.toStdString()); - } -} - -QmitkInputDevicesPrefPage::QmitkInputDevicesPrefPage() -: m_MainControl(nullptr) -{ -} - -void QmitkInputDevicesPrefPage::Init(berry::IWorkbench::Pointer ) -{ -} - -void QmitkInputDevicesPrefPage::CreateQtControl(QWidget* parent) -{ - m_MainControl = new QWidget(parent); - auto layout = new QVBoxLayout; - - QList temp(GetInputDeviceRegistry()->GetInputDevices()); - - for(QList::const_iterator it = temp.begin(); it != temp.end();++it) - { - QString inputDeviceName((*it)->GetName()); - auto checkBox = new QCheckBox((inputDeviceName),m_MainControl); - layout->addWidget(checkBox); - m_InputDevices.insert(checkBox,(*it)->GetID()); - - if(inputDeviceName == "WiiMote") - { - m_WiiMoteModes = new QGroupBox("WiiMote Modus"); - - m_WiiMoteHeadTracking = new QRadioButton(mitk::CoreExtConstants::WIIMOTE_HEADTRACKING); - m_WiiMoteSurfaceInteraction = new QRadioButton(mitk::CoreExtConstants::WIIMOTE_SURFACEINTERACTION); - m_WiiMoteHeadTracking->setChecked(true); - - auto vBoxLayout = new QVBoxLayout; - - vBoxLayout->addWidget(m_WiiMoteHeadTracking); - vBoxLayout->addWidget(m_WiiMoteSurfaceInteraction); - - m_WiiMoteModes->setLayout(vBoxLayout); - - layout->addWidget(m_WiiMoteModes); - } - - } - - layout->addStretch(); - m_MainControl->setLayout(layout); - this->Update(); -} - -QWidget* QmitkInputDevicesPrefPage::GetQtControl() const -{ - return m_MainControl; -} - -bool QmitkInputDevicesPrefPage::PerformOk() -{ - auto* prefs = GetPreferences(); - bool result = true; - - mitk::IInputDeviceRegistry* inputDeviceRegistry = GetInputDeviceRegistry(); - - QHashIterator it(m_InputDevices); - while (it.hasNext()) - { - it.next(); - mitk::IInputDeviceDescriptor::Pointer inputdevice(inputDeviceRegistry->Find(it.value())); - - if(it.value() == mitk::CoreExtConstants::WIIMOTE_XMLATTRIBUTE_NAME) - { - const auto headTracking(m_WiiMoteHeadTracking->text().toStdString()); - const auto surfaceInteraction(m_WiiMoteSurfaceInteraction->text().toStdString()); - - prefs->PutBool(headTracking, m_WiiMoteHeadTracking->isChecked()); - prefs->PutBool(surfaceInteraction, m_WiiMoteSurfaceInteraction->isChecked()); - - // forced flush of the preferences is needed - // because otherwise the mitk::WiiMoteActivator class - // cannot distinguish the two different modes without - // changing the interface for all input devices - prefs->Flush(); - } - - if(it.key()->isChecked()) - { - result &= inputdevice->CreateInputDevice()->RegisterInputDevice(); - } - else - { - result &= inputdevice->CreateInputDevice()->UnRegisterInputDevice(); - - // temporary fix, unclean solution: - // e.g. user activates SpaceNavigator and leaves the - // the wiimote deactivated, the user will get the warning - // despite the fact that it has never been activated - if(it.value() == mitk::CoreExtConstants::WIIMOTE_XMLATTRIBUTE_NAME) - { - // until now 2010-09-06 there were some unfixed problems - // with reconnecting the wiimote after disconnecting it. - // It was suggested that it might have something to do - // with the type of stack, that is used for the pairing. - // MS-Stack for example does not work properly. - QMessageBox::information(nullptr,"WiiMote supportproblem", - "A reconnect of the WiiMote is not yet supported! " - "Please restart the application, if you want to " - "activate the Wii remote/s again."); - } - } - - if(result) - { - prefs->PutBool(it.value().toStdString(), it.key()->isChecked()); - } - } - return result; -} - -void QmitkInputDevicesPrefPage::PerformCancel() -{ - -} - -void QmitkInputDevicesPrefPage::Update() -{ - auto* prefs = GetPreferences(); - QHashIterator it(m_InputDevices); - - while (it.hasNext()) - { - it.next(); - it.key()->setChecked(prefs->GetBool(it.value().toStdString(), false)); - if(it.value() == mitk::CoreExtConstants::WIIMOTE_XMLATTRIBUTE_NAME) - { - m_WiiMoteHeadTracking->setChecked( - prefs->GetBool(mitk::CoreExtConstants::WIIMOTE_HEADTRACKING.toStdString(), false)); - m_WiiMoteSurfaceInteraction->setChecked - (prefs->GetBool(mitk::CoreExtConstants::WIIMOTE_SURFACEINTERACTION.toStdString(), false)); - } - } -} - -mitk::IInputDeviceRegistry *QmitkInputDevicesPrefPage::GetInputDeviceRegistry() const -{ - ctkServiceReference serviceRef = QmitkCommonExtPlugin::getContext()->getServiceReference(); - if (!serviceRef) return nullptr; - - return QmitkCommonExtPlugin::getContext()->getService(serviceRef); -} diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.h b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.h deleted file mode 100644 index 0f1b91d115..0000000000 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkInputDevicesPrefPage.h +++ /dev/null @@ -1,87 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef QmitkInputDevicesPrefPage_h -#define QmitkInputDevicesPrefPage_h - -#include "berryIQtPreferencePage.h" - -#include -#include -#include - -class QWidget; -class QCheckBox; - -namespace mitk { -struct IInputDeviceRegistry; -} - -class QmitkInputDevicesPrefPage : public QObject, public berry::IQtPreferencePage -{ - Q_OBJECT - Q_INTERFACES(berry::IPreferencePage) - -public: - - /** - * Default constructor - */ - QmitkInputDevicesPrefPage(); - - /** - * @see berry::IPreferencePage::Init(berry::IWorkbench::Pointer workbench) - */ - void Init(berry::IWorkbench::Pointer workbench) override; - - /** - * @see berry::IPreferencePage::CreateQtControl(void* parent) - */ - void CreateQtControl(QWidget* widget) override; - - /** - * @see berry::IPreferencePage::CreateQtControl() - */ - QWidget* GetQtControl() const override; - - /** - * @see berry::IPreferencePage::PerformOk() - */ - bool PerformOk() override; - - /** - * @see berry::IPreferencePage::PerformCancel() - */ - void PerformCancel() override; - - /** - * @see berry::IPreferencePage::Update() - */ - void Update() override; - -protected: - - QWidget* m_MainControl; - - QHash m_InputDevices; - - // specific for Wiimote - QGroupBox* m_WiiMoteModes; - QRadioButton* m_WiiMoteHeadTracking; - QRadioButton* m_WiiMoteSurfaceInteraction; - - mitk::IInputDeviceRegistry* GetInputDeviceRegistry() const; - -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp index 4b97be0319..55d3bc0f52 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp @@ -1,698 +1,705 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMovieMakerView.h" #include #include "QmitkAnimationItemDelegate.h" #include "QmitkOrbitAnimationItem.h" #include "QmitkOrbitAnimationWidget.h" #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include "QmitkTimeSliceAnimationItem.h" #include "QmitkTimeSliceAnimationWidget.h" #include #include #include #include #include #include #include namespace { QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) { if (widgetKey == "Orbit") return new QmitkOrbitAnimationItem; if (widgetKey == "Slice") return new QmitkSliceAnimationItem; if (widgetKey == "Time") return new QmitkTimeSliceAnimationItem; return nullptr; } class EncodingThread : public QThread { public: EncodingThread(mitk::VideoRecorder* videoRecorder, QObject* parent = nullptr) : QThread(parent), m_VideoRecorder(videoRecorder) { } ~EncodingThread() override = default; private: void run() override { - m_VideoRecorder->StopRecording(); + try + { + m_VideoRecorder->StopRecording(); + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e.GetDescription(); + } } mitk::VideoRecorder* m_VideoRecorder; }; } const std::string QmitkMovieMakerView::VIEW_ID = "org.mitk.views.moviemaker"; QmitkMovieMakerView::QmitkMovieMakerView() : m_Parent(nullptr), m_Ui(new Ui::QmitkMovieMakerView), m_AnimationModel(nullptr), m_AddAnimationMenu(nullptr), m_RecordMenu(nullptr), m_Timer(nullptr), m_TotalDuration(0.0), m_NumFrames(0), m_CurrentFrame(0) { } QmitkMovieMakerView::~QmitkMovieMakerView() { } void QmitkMovieMakerView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Ui->setupUi(parent); this->InitializeAnimationWidgets(); this->InitializeAnimationTreeViewWidgets(); this->InitializePlaybackAndRecordWidgets(); this->InitializeTimer(parent); m_Ui->animationWidgetGroupBox->setVisible(false); } void QmitkMovieMakerView::InitializeAnimationWidgets() { m_AnimationWidgets["Orbit"] = new QmitkOrbitAnimationWidget; m_AnimationWidgets["Slice"] = new QmitkSliceAnimationWidget; m_AnimationWidgets["Time"] = new QmitkTimeSliceAnimationWidget; for (const auto& widget : m_AnimationWidgets) { if (nullptr != widget.second) { widget.second->setVisible(false); m_Ui->animationWidgetGroupBoxLayout->addWidget(widget.second); } } this->ConnectAnimationWidgets(); } void QmitkMovieMakerView::InitializeAnimationTreeViewWidgets() { this->InitializeAnimationModel(); this->InitializeAddAnimationMenu(); this->ConnectAnimationTreeViewWidgets(); } void QmitkMovieMakerView::InitializePlaybackAndRecordWidgets() { this->InitializeRecordMenu(); this->ConnectPlaybackAndRecordWidgets(); this->InitializeRecordingProgress(); } void QmitkMovieMakerView::InitializeAnimationModel() { m_AnimationModel = new QStandardItemModel(m_Ui->animationTreeView); m_AnimationModel->setHorizontalHeaderLabels(QStringList() << "Animation" << "Timeline"); m_Ui->animationTreeView->setModel(m_AnimationModel); m_Ui->animationTreeView->setItemDelegate(new QmitkAnimationItemDelegate(m_Ui->animationTreeView)); } void QmitkMovieMakerView::InitializeAddAnimationMenu() { m_AddAnimationMenu = new QMenu(m_Ui->addAnimationButton); for(const auto& widget : m_AnimationWidgets) m_AddAnimationMenu->addAction(widget.first); } void QmitkMovieMakerView::InitializeRecordMenu() { std::array, 4> renderWindows = { std::make_pair(QStringLiteral("Axial"), QStringLiteral("stdmulti.widget0")), std::make_pair(QStringLiteral("Sagittal"), QStringLiteral("stdmulti.widget1")), std::make_pair(QStringLiteral("Coronal"), QStringLiteral("stdmulti.widget2")), std::make_pair(QStringLiteral("3D"), QStringLiteral("stdmulti.widget3")) }; m_RecordMenu = new QMenu(m_Ui->recordButton); for(const auto& renderWindow : renderWindows) { auto* action = new QAction(m_RecordMenu); action->setText(renderWindow.first); action->setData(renderWindow.second); m_RecordMenu->addAction(action); } } void QmitkMovieMakerView::InitializeRecordingProgress() { m_Ui->recordingLabel->setEnabled(true); m_Ui->recordingLabel->setVisible(false); m_Ui->recordingProgressBar->setEnabled(true); m_Ui->recordingProgressBar->setValue(0); m_Ui->recordingProgressBar->setVisible(false); m_Ui->encodingLabel->setEnabled(false); m_Ui->encodingLabel->setVisible(false); m_Ui->encodingProgressBar->setEnabled(false); m_Ui->encodingProgressBar->setMaximum(1); m_Ui->encodingProgressBar->setVisible(false); } void QmitkMovieMakerView::InitializeTimer(QWidget* parent) { m_Timer = new QTimer(parent); this->OnFPSSpinBoxValueChanged(m_Ui->fpsSpinBox->value()); this->ConnectTimer(); } void QmitkMovieMakerView::ConnectAnimationTreeViewWidgets() { connect(m_AnimationModel, &QStandardItemModel::rowsInserted, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsInserted); connect(m_AnimationModel, &QStandardItemModel::rowsRemoved, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved); connect(m_Ui->animationTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged); connect(m_Ui->moveAnimationUpButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationUpButtonClicked); connect(m_Ui->moveAnimationDownButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationDownButtonClicked); connect(m_Ui->addAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnAddAnimationButtonClicked); connect(m_Ui->removeAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRemoveAnimationButtonClicked); } void QmitkMovieMakerView::ConnectAnimationWidgets() { connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDurationSpinBoxValueChanged(double))); connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDelaySpinBoxValueChanged(double))); } void QmitkMovieMakerView::ConnectPlaybackAndRecordWidgets() { connect(m_Ui->playButton, &QToolButton::toggled, this, &QmitkMovieMakerView::OnPlayButtonToggled); connect(m_Ui->stopButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnStopButtonClicked); connect(m_Ui->recordButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRecordButtonClicked); connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnFPSSpinBoxValueChanged(int))); } void QmitkMovieMakerView::ConnectTimer() { connect(m_Timer, &QTimer::timeout, this, &QmitkMovieMakerView::OnTimerTimeout); } void QmitkMovieMakerView::SetFocus() { m_Ui->addAnimationButton->setFocus(); } void QmitkMovieMakerView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { auto multiWidgetEditor = dynamic_cast(renderWindowPart); bool isMxN = nullptr != multiWidgetEditor && multiWidgetEditor->GetClassName() == "QmitkMxNMultiWidgetEditor"; m_Parent->setDisabled(isMxN); } void QmitkMovieMakerView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { } void QmitkMovieMakerView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { } void QmitkMovieMakerView::OnMoveAnimationUpButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int selectedRow = selection[0].top(); if (selectedRow > 0) m_AnimationModel->insertRow(selectedRow - 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnMoveAnimationDownButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); if (selectedRow < rowCount - 1) m_AnimationModel->insertRow(selectedRow + 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnAddAnimationButtonClicked() { auto action = m_AddAnimationMenu->exec(QCursor::pos()); if (nullptr != action) { const auto key = action->text(); m_AnimationModel->appendRow(QList() << new QStandardItem(key) << CreateDefaultAnimation(key)); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); } } void QmitkMovieMakerView::OnPlayButtonToggled(bool checked) { if (checked) { m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-pause.svg")); m_Ui->playButton->repaint(); m_Timer->start(static_cast(1000.0 / m_Ui->fpsSpinBox->value())); } else { m_Timer->stop(); m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Ui->playButton->repaint(); } } void QmitkMovieMakerView::OnStopButtonClicked() { m_Ui->playButton->setChecked(false); m_Ui->stopButton->setEnabled(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } void QmitkMovieMakerView::OnRecordButtonClicked() { if (0 == m_NumFrames || 0.0 == m_TotalDuration) return; m_VideoRecorder = std::make_unique(); if (m_VideoRecorder->GetFFmpegPath().empty()) { QMessageBox::information(nullptr, "Movie Maker", "

Set path to FFmpeg (ffmpeg.org) in preferences " "(Window -> Preferences... (Ctrl+P) -> Movie Maker) to be able to record your " "movies to video files.

"); return; } auto action = m_RecordMenu->exec(QCursor::pos()); if (nullptr == action) return; m_VideoRecorder->SetRenderWindowName(action->data().toString().toStdString()); m_VideoRecorder->SetFrameRate(static_cast(m_Ui->fpsSpinBox->value())); auto fileExt = QString::fromStdString(mitk::VideoRecorder::GetFileExtension(m_VideoRecorder->GetOutputFormat())); QString outputPath = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*" + fileExt + ")"); if (outputPath.isEmpty()) return; if(!outputPath.endsWith(fileExt)) outputPath += fileExt; m_VideoRecorder->SetOutputPath(outputPath.toStdString()); m_Ui->recordButton->setEnabled(false); m_Ui->recordingProgressBar->setMaximum(m_NumFrames); m_Ui->recordingLabel->setVisible(true); m_Ui->recordingProgressBar->setVisible(true); m_Ui->encodingLabel->setVisible(true); m_Ui->encodingProgressBar->setVisible(true); EncodingThread* encodingThread = new EncodingThread(m_VideoRecorder.get(), m_Parent); connect(encodingThread, &EncodingThread::finished, this, &QmitkMovieMakerView::OnEncodingFinished); try { m_VideoRecorder->StartRecording(); for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { m_Ui->recordingProgressBar->setValue(m_CurrentFrame + 1); this->RenderCurrentFrame(); m_VideoRecorder->RecordFrame(); } m_Ui->encodingProgressBar->setMaximum(0); m_Ui->encodingLabel->setEnabled(true); m_Ui->encodingProgressBar->setEnabled(true); encodingThread->start(); } catch (const mitk::Exception& exception) { if (encodingThread->isRunning()) encodingThread->terminate(); QMessageBox::critical(nullptr, "Movie Maker", exception.GetDescription()); m_VideoRecorder = nullptr; this->OnEncodingFinished(); } } void QmitkMovieMakerView::OnEncodingFinished() { m_VideoRecorder = nullptr; this->InitializeRecordingProgress(); m_CurrentFrame = 0; this->RenderCurrentFrame(); m_Ui->recordButton->setEnabled(true); } void QmitkMovieMakerView::OnRemoveAnimationButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) m_AnimationModel->removeRow(selection[0].top()); } void QmitkMovieMakerView::OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int) { this->CalculateTotalDuration(); m_Ui->animationTreeView->selectionModel()->select( m_AnimationModel->index(start, 0, parent), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int) { this->CalculateTotalDuration(); this->UpdateWidgets(); } void QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&) { this->UpdateWidgets(); } void QmitkMovieMakerView::OnStartComboBoxCurrentIndexChanged(int index) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetStartWithPrevious(index); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDurationSpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDuration(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDelaySpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDelay(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnFPSSpinBoxValueChanged(int value) { this->CalculateTotalDuration(); m_Timer->setInterval(static_cast(1000.0 / value)); } void QmitkMovieMakerView::OnTimerTimeout() { this->RenderCurrentFrame(); m_CurrentFrame = std::min(m_NumFrames, m_CurrentFrame + 1); if (m_CurrentFrame >= m_NumFrames) { m_Ui->playButton->setChecked(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } m_Ui->stopButton->setEnabled(m_CurrentFrame != 0); } void QmitkMovieMakerView::RenderCurrentFrame() { const double deltaT = m_TotalDuration / (m_NumFrames - 1); const auto activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); for (const auto& animation : activeAnimations) { const auto nextActiveAnimations = this->GetActiveAnimations((m_CurrentFrame + 1) * deltaT); bool lastFrameForAnimation = true; for (const auto& nextAnimation : nextActiveAnimations) { if (nextAnimation.first == animation.first) { lastFrameForAnimation = false; break; } } animation.first->Animate(!lastFrameForAnimation ? animation.second : 1.0); } mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void QmitkMovieMakerView::UpdateWidgets() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (selection.isEmpty()) { m_Ui->moveAnimationUpButton->setEnabled(false); m_Ui->moveAnimationDownButton->setEnabled(false); m_Ui->removeAnimationButton->setEnabled(false); m_Ui->playbackAndRecordingGroupBox->setEnabled(false); this->HideCurrentAnimationWidget(); } else { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); m_Ui->moveAnimationUpButton->setEnabled(rowCount > 1 && selectedRow != 0); m_Ui->moveAnimationDownButton->setEnabled(rowCount > 1 && selectedRow < rowCount - 1); m_Ui->removeAnimationButton->setEnabled(true); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); this->ShowAnimationWidget(dynamic_cast(m_AnimationModel->item(selectedRow, 1))); } this->UpdateAnimationWidgets(); } void QmitkMovieMakerView::UpdateAnimationWidgets() { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { m_Ui->startComboBox->setCurrentIndex(item->GetStartWithPrevious()); m_Ui->durationSpinBox->setValue(item->GetDuration()); m_Ui->delaySpinBox->setValue(item->GetDelay()); m_Ui->animationGroupBox->setEnabled(true); } else { m_Ui->animationGroupBox->setEnabled(false); } } void QmitkMovieMakerView::HideCurrentAnimationWidget() { if (m_Ui->animationWidgetGroupBox->isVisible()) { m_Ui->animationWidgetGroupBox->setVisible(false); int numWidgets = m_Ui->animationWidgetGroupBoxLayout->count(); for (int i = 0; i < numWidgets; ++i) m_Ui->animationWidgetGroupBoxLayout->itemAt(i)->widget()->setVisible(false); } } void QmitkMovieMakerView::ShowAnimationWidget(QmitkAnimationItem* animationItem) { this->HideCurrentAnimationWidget(); if (animationItem == nullptr) return; const QString widgetKey = animationItem->GetWidgetKey(); auto animationWidgetIter = m_AnimationWidgets.find(widgetKey); auto animationWidget = m_AnimationWidgets.end() != animationWidgetIter ? animationWidgetIter->second : nullptr; if (nullptr != animationWidget) { m_Ui->animationWidgetGroupBox->setTitle(widgetKey); animationWidget->SetAnimationItem(animationItem); animationWidget->setVisible(true); } m_Ui->animationWidgetGroupBox->setVisible(animationWidget != nullptr); } void QmitkMovieMakerView::RedrawTimeline() { if (m_AnimationModel->rowCount() > 1) { m_Ui->animationTreeView->dataChanged( m_AnimationModel->index(0, 1), m_AnimationModel->index(m_AnimationModel->rowCount() - 1, 1)); } } QmitkAnimationItem* QmitkMovieMakerView::GetSelectedAnimationItem() const { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); return !selection.isEmpty() ? dynamic_cast(m_AnimationModel->item(selection[0].top(), 1)) : nullptr; } void QmitkMovieMakerView::CalculateTotalDuration() { const int rowCount = m_AnimationModel->rowCount(); double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { auto item = dynamic_cast(m_AnimationModel->item(i, 1)); if (nullptr == item) continue; if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } m_TotalDuration = totalDuration; m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); } std::vector> QmitkMovieMakerView::GetActiveAnimations(double t) const { const int rowCount = m_AnimationModel->rowCount(); std::vector> activeAnimations; double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); if (item == nullptr) continue; if (item->GetDuration() > 0.0) { double start = item->GetStartWithPrevious() ? previousStart + item->GetDelay() : totalDuration + item->GetDelay(); if (start <= t && t <= start + item->GetDuration()) activeAnimations.emplace_back(item, (t - start) / item->GetDuration()); } if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } return activeAnimations; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index 387ed6ae69..21d26e1e40 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,435 +1,435 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the Segmentation View",2.00} \tableofcontents \section org_mitk_views_segmentationoverview Overview Segmentation is the act of separating an image into foreground and background subsets by manual or automated delineation, while the foreground is defined to be part of the segmentation. Such a segmented image subset is also called a label as it typically labels a specific region of interest. A multilabel segmentation may contain multiple labels organized in distinct groups. You can create multiple labels for different regions of interest contained within a single segmentation image. Labels in the same group cannot overlap each other but labels from different groups may overlap. The MITK Segmentation Plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of two views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Post-processing of segmentations
In this user guide, the features of the Segmentation View are described. For an introduction to the Segmentation Utilities, refer to the respective user guide. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation View", 16.00} \section org_mitk_views_segmentationtechnicalissues Image and segmentation prerequisites The Segmentation View has a few prerequisites regarding the segmentations and their reference image:
  • Images must be two or three-dimensional and may be either static or dynamic, e.g., are time-resolved resp. have different pixel values for different time steps.
  • Images must be single-valued, i.e. CT, MRI or ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations must be congruent to their reference images.
\section org_mitk_views_segmentationdataselection Image selection and creating new segmentations To select a reference image for a new segmentation, click on the Image widget in the Data selection section at the very top of the Segmentation View. Choose an image from the displayed list of Data Manager images. Once an image is selected, a new segmentation for this reference image can be created by clicking the button right next to the Segmentation widget in the Data selection section. A new multilabel segmentation with an initial, empty label is automatically generated if not set otherwise in the preferences. The new segmentation will be added to the Data Manager as child node of its reference image node. It is automatically selected edit can be edited in the Segmentation View right away. Instead of creating a new segmentation, an existing segmentation can be selected and edited as well. The selection list of existing segmentations for a certain reference image consists of matching/congruent segmentations only. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection and creating new segmentations",12} \section org_mitk_views_segmentationgroups Groups Segmentation images consist of at least a single group called "Group 0" in which the first default label is created. More groups can be added and removed but there will always be at least a single group. Labels of the same group cannot overlap each other. Labels of different groups may overlap each other. For example, you could segment the whole heart as "Heart" label in "Group 0", add "Group 1" and create multiple labels of the anatomical details of the heart in that group. Naturally, all these labels lie within the extents of the "Heart" label of "Group 0" but in principle they are completely independent of "Group 0". Some pixels are now labelled twice, e.g., as "Heart" and "Left ventricle". Since the labels of "Group 1" cannot overlap each other, it is impossible to accidentally label a pixel as both "Left ventricle" and "Right ventricle". If you would like to segment even more details you could create "Group 2" to have up to three labels per pixel. Nevertheless, groups are technically a flat data structure and cannot contain nested groups. It is all about possibly overlapping labels from distinct groups and spatially exclusive, non-overlapping labels within the same group. \imageMacro{"QmitkSegmentation_Groups.png","Groups",10} \section org_mitk_views_segmentationlabelinstances Label instances The Segmentation View also supports label instances. That is, segmenting multiple distributed entities of the same thing like metastases for example. A label is already a single instance of itself but for the sake of clearness it is only shown explicitly as such as soon as there are multiple instances of the same label (also refered to as label class). Technically all label instances have their own distinct label/pixel value that is explicitly shown in square brackets as a clue for distinction and identification. It is important to understand that this number is not a classic index starting at zero for each label class. It is just the plain label/pixel value of the label instance, which is unique across all labels of the whole segmentation. \imageMacro{"QmitkSegmentation_LabelInstances.png","Label instances",10} \section org_mitk_views_segmentationlock_color_visibility Unlocking, changing color of, and hiding labels Labels (and label instances) are locked by default: labels from the same group cannot accidentally override pixels from other labels. Locked labels behave like cookie cutters for other labels of the same group. You can unlock labels to remove that protection from other labels of the same group. Their pixel contents can then be overridden by other labels of the same group. Remember that labels from distinct groups do not interact with each other. They can always overlap (not override) each other. You can also change the color of labels and label instances as well as show (default) or hide their pixel contents. The icons at the right side of the rows of the groups and labels widget reflect their state in all these regards. Renaming of labels can be found in their content menu as shown further below. \imageMacro{"QmitkSegmentation_LockColorVisibility.png","Unlocking\, changing color of\, and hiding labels",10} \section org_mitk_views_segmentationcontextmenus Context menus Actions for organization of groups, labels, and label instances (as well as other operations) can be also found in their right-click context menus. \imageMacro{"QmitkSegmentation_ContextMenus.png","Context menus of groups\, labels\, and label instances",12} Most actions available in these context menus are self-explanatory or were already described above by other means of access like the tool button bar for adding and removing groups, labels, and label instances. Labels and label instances can be renamed, while groups have fixed names. This may change in future releases of MITK. Clear content only clears the pixels of a label or label instance but won't delete the actual label or label instance. Groups can be locked and unlocked as a whole from their context menu, while label instances can be directly locked and unlocked outside the context menu as decribed further below. \section org_mitk_views_segmentationlabelsuggestions Label name and color suggestions When renaming labels or creating new labels with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. The list of predefined label names and colors for the auto-completion feature can be either extented or replaced by a custom list of label name and color suggestions. This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. \section org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all labels in all groups like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label for a certain value already exists, its properties are overridden by the preset. If a label for a certain value does not yet exist, an empty label with the label properties of the preset is created. The actual segmentations of labels are unaffected as label set presets only store label properties. \imageMacro{QmitkSegmentation_Preset.png,"Saving and loading label set presets", 10.00} \subsection org_mitk_views_segmentationdefaultlabelpresets Applying label set presets by default If you work on a repetetive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "Label 1" label. \section org_mitk_views_segmentationpreferences Preferences The Segmentation Plugin offers a number of preferences which can be set via the MITK Workbench application preferences (Ctrl+P): \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Compact view: Hide the tool button texts to save some space on screen (6 instead of 4 buttons per row)
  • 2D display: Draw segmentations as as outlines or transparent overlays
  • Data node selection mode: Hide everything but the selected segmentation and its reference image
  • Default label set preset: Start a new segmentation with this preset instead of a default label
  • Label creation: Assign default names and colors to new labels or ask users for name and color
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtooloverview Segmentation tool overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting / adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as any of the manual segmentation tools are active - otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationaddsubtracttools Add and subtract tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and subtract tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationlassotool Lasso tool \imageMacro{QmitkSegmentation_Lasso.png,"Lasso tool",7.70} The tool is a more advanced version of the add/subtract tool. It offers you the following features:
  1. Generating a polygon segmentation (click left mouse button to set ancor point)
  2. Freehand contouring (like the add tool; press left mouse button while moving the mouse)
  3. Move ancor points (select an ancor point, press left mouse button while moving the mouse)
  4. Add new ancor points (press CTRL while click left mouse to add an ancor to the contour)
  5. Delete an ancor point (press Del while ancor point is selected)
  6. Segmentation can be added to the label (Add mode) or subtracted (Subtract mode)
To start a segmentation double left click where the first ancor point should be. To end the segmentation double left click where the last ancor point should be. Please note that:
  • feature 3-6 are only available, if auto confirm is *not* activated
  • feature 3-5 is not available for freehand contour segments
\subsection org_mitk_views_segmentationpaintwipetools Paint and wipe tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and wipe tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationregiongrowingtool Region growing tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region growing tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This allows to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up / down is different from left / right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool Fill tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill tool",3.81} Left-click inside a region/segmentation to flood fill all connected pixels that have the same label with the active label. This tool will only work on regions of unlocked labels or on regions that are not labeled at all. \subsection org_mitk_views_segmentationerasetool Erase tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmented regions (left-click on specific segmentation) or to clear a whole slice at once (left-click at the non-labeled background). This tool will only work and regions of unlocked labels or on regions of the active label. \subsection org_mitk_views_segmentationclosetool Close tool \imageMacro{QmitkSegmentation_IMGIconClose.png,"Close tool",3.79} Left-click inside the region/segmentation to fill all "holes" (pixels labelled with another label or no label) inside the region. Therefore this tool behaves like a local closing operation. This tool will not work, when a non-labeled region is selected and holes of locked labels will not be filled. \remark This tool always uses the label of the selected region (even if this label is not the active label). Therefore you can use this tool on regions of the active label and of none locked labels (without the need to change the active label). \subsection org_mitk_views_segmentationlivewiretool Live wire tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live wire tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00} The tool handling is the same like the Lasso tool (see for more info), except it generates live wire contours instead of straight lines. \subsection org_mitk_views_segmentationinterpolation 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. Note: Interpolation is currently disabled for segmentations containing more than one label. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D interpolation usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D interpolation usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since undo / redo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentation3dsegmentation 3D segmentation tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentation3dthresholdtool 3D Threshold tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentation3dulthresholdTool 3D upper / lower threshold tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D upper / lower threshold tool",10.00} \subsection org_mitk_views_segmentation3dotsutool 3D Otsu tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentation3dgrowcuttool 3D GrowCut tool The 3D GrowCut tool uses previously created segmentation labels (e.g. by the "Add"-tool) stored in the segmentation layer 0. The GrowCut tool will use these segmentation labels to create a seedimage that will serve as input to the algorithm. As an advanced setting option, a Distance penalty can be set, which increases the cohesion in the immediate surroundings of the initial labels. Based on the seedimage and the Distance penalty, a growing is started, which includes all areas that are not initially assigned to a specific label. During this process, initially unassigned areas are assigned to the best fitting labels. After the segmentation process, the user can decide which newly generated labels should be confirmed. \imageMacro{QmitkSegmentation_3DGrowCutTool.png,"3D GrowCut tool",16.00} \subsection org_mitk_views_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "islands" within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} -This tool provides a GUI to the deep learning-based segmentation algorithm called the nnUNet. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) +This tool provides a GUI to the deep learning-based segmentation algorithm called the nnU-Net v1. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
-Remark: The tool assumes that you have a Python3 environment with nnUNet (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. +Remark: The tool assumes that you have a Python3 environment with nnU-Net v1 (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. -# For ensemble predictions, you will get the option to select parameters irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded and shown next to "Required Modalities". Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc Miscellaneous: -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# The "Advanced" > "Available Models" lists the available pre-trained tasks for download. Make sure you have internet connection. Then, choose a Task from the dropdown and click the Download button. The pre-trained models for the selected Task will be downloaded and placed to the RESULTS_FOLDER directory automatically. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \subsection org_mitk_views_segmentationTotalSegmentator TotalSegmentator Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"TotalSegmentator tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the TotalSegmentator. With this tool, you can get a segmentation mask predicted for 104 classes in CT images, loaded in MITK. For a detailed explanation on tasks and supported classes etc., please refer to https://github.com/wasserth/TotalSegmentator
The tool assumes that you have Python 3 installed and available on your machine. We recommend to install TotalSegmentator via MITK. The "Install TotalSegmentator" action implicitly creates a python virtual environment in an MITK mainitained directory. Note: on Debian/Ubuntu systems, you need to install the python3-venv package using the following command: `apt install python3-venv`. For best results, your machine should be ideally equipped with a CUDA-enabled GPU. \imageMacro{QmitkSegmentation_TotalsegmentatorTool.png, "TotalSegmentator Settings",5} \subsubsection org_mitk_views_segmentationTotalSegmentatorWorkflow Workflow: -# Install TotalSegmentator: Click "Install TotalSegmentator" to install TotalSegmentator (version: 1.5.5) in a virtual environment. Make sure you have a working internet connection. This might take a while. It is a one time job, though. Once installed, the "Install TotalSegmentator" button is grayed out. -# If Python is not found by MITK goto "Install Options" & select the "System Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting Python installation for TotalSegmentator to use or click "Select" in the dropdown to choose an unlisted installation of Python. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "/usr/bin/". No need to navigate all the way into "../usr/bin/python3", for example. -# Select a specific subtask in the "Tasks" drop-downs. The default is "total" for non-specific total segmentation. -# Click on "Run TotalSegmentator" for a preview. -# In the "Advanced" section, you can also activate other options like "Fast" for faster runtime and less memory requirements. Use "Fast" if you only have a CPU for inferencing. -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# In case you want to use your own virtual environment containing TotalSegmentator, goto "Install Options" & check "Use Custom Installation" checkbox. Then, select the environment of your choice by using "Custom Env. Path". \section org_mitk_views_segmentationpostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3dtimages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationtechnicaldetail Technical information for developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions. */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 634229e85d..18bdafea54 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1031 +1,1083 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include namespace { QList Get2DWindows(const QList allWindows) { QList all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); + this->RemoveObserversFromWorkingImage(); + // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= - mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); + mitk::MessageDelegate(this, &Self::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; + this->RemoveObserversFromWorkingImage(); + // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); + + this->AddObserversToWorkingImage(); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } +void QmitkSegmentationView::OnLabelAdded(mitk::LabelSetImage::LabelValueType) +{ + this->ValidateSelectionInput(); +} + +void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) +{ + this->ValidateSelectionInput(); +} + +void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) +{ + this->ValidateSelectionInput(); +} + +mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() +{ + if (m_WorkingNode.IsNull()) + return nullptr; + + return dynamic_cast(m_WorkingNode->GetData()); +} + +void QmitkSegmentationView::AddObserversToWorkingImage() +{ + auto* workingImage = this->GetWorkingImage(); + + if (workingImage != nullptr) + { + workingImage->AddLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); + workingImage->AddLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); + workingImage->AddGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); + } +} + +void QmitkSegmentationView::RemoveObserversFromWorkingImage() +{ + auto* workingImage = this->GetWorkingImage(); + + if (workingImage != nullptr) + { + workingImage->RemoveLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); + workingImage->RemoveLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); + workingImage->RemoveGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); + } +} + void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); return; } QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); - connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); + connect(visibilityShortcut, &QShortcut::activated, this, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); - connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); + connect(labelToggleShortcut, &QShortcut::activated, this, &Self::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, - this, &QmitkSegmentationView::OnReferenceSelectionChanged); + this, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, - this, &QmitkSegmentationView::OnSegmentationSelectionChanged); + this, &Self::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire' SAM"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, - this, &QmitkSegmentationView::OnManualTool2DSelected); + this, &Self::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections - connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); + connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &Self::OnNewSegmentation); - connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); + connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); - connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &QmitkSegmentationView::OnCurrentLabelSelectionChanged); - connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &QmitkSegmentationView::OnGoToLabel); - connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &QmitkSegmentationView::OnLabelRenameRequested); + connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); + connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); + connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += - mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); + mitk::MessageDelegate(this, &Self::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= - mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); + mitk::MessageDelegate(this, &Self::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); - auto activeLayer = labelSetImage->GetActiveLayer(); - numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); + numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h index 93d2058441..0d71e0274c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h @@ -1,184 +1,175 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSegmentationView_h #define QmitkSegmentationView_h #include "ui_QmitkSegmentationViewControls.h" #include #include /** * @brief The segmentation view provides a set of tool to use different segmentation algorithms. * It provides two selection widgets to load an image node and a segmentation node * on which to perform the segmentation. Creating new segmentation nodes is also possible. * The available segmentation tools are grouped into "2D"- and "3D"-tools. * * Most segmentation tools / algorithms need some kind of user interaction, where the * user is asked to draw something in the image display or set some seed points / start values. * The tools also often provide additional propeties so that a user can modify the * algorithm's behavior. * * This class additionally provides options to work with different layers (create new layers, * switch between layers). * Moreover, a multilabel widget displays all the existing labels of a multilabel segmentation * for the currently active layer. * The multilabel widget allows to control the labels by creatin new one, removing existing ones, * showing / hiding single labels, merging labels, (re-)naming them etc. * * Additionally the view provides an option to create "2D"- and "3D"-interpolations between * neighboring segmentation masks on unsegmented slices. * Interpolation for multilabel segmentations is currently not implemented. */ class QmitkSegmentationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkSegmentationView(); ~QmitkSegmentationView() override; private Q_SLOTS: // reaction to the selection of a new reference image in the selection widget void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new segmentation image in the selection widget void OnSegmentationSelectionChanged(QList nodes); // reaction to the shortcut ("CTRL+H") for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut ("CTRL+L") for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the button "New segmentation" void OnNewSegmentation(); void OnManualTool2DSelected(int id); void OnShowMarkerNodes(bool); void OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&); void OnLabelRenameRequested(mitk::Label* label, bool rename) const; + void OnLabelAdded(mitk::LabelSetImage::LabelValueType labelValue); + void OnLabelRemoved(mitk::LabelSetImage::LabelValueType labelValue); + void OnGroupRemoved(mitk::LabelSetImage::GroupIndexType groupIndex); + private: + using Self = QmitkSegmentationView; + + mitk::LabelSetImage* GetWorkingImage(); + void AddObserversToWorkingImage(); + void RemoveObserversFromWorkingImage(); + void CreateQtPartControl(QWidget* parent) override; void SetFocus() override {} /** * @brief Enable or disable the SegmentationInteractor. * * The active tool is retrieved from the tool manager. * If the active tool is valid, the SegmentationInteractor is enabled * to listen to 'SegmentationInteractionEvent's. */ void ActiveToolChanged(); - /** - * @brief Test whether the geometry of the reference image - * fits the world geometry of the respective renderer. - * - * The respective renderer is retrieved from the given event, which - * needs to be a 'SegmentationInteractionEvent'. - * This event provides not only the sending base renderer but also a - * bool that indicates whether the mouse cursor entered or left the - * base renderer. - * This information is used to compare the renderer's world geometry - * with the oriented time geometry of the current reference image. - * If the geometries align, the renderer is not blocked anymore and the - * view's warning message is removed. - * If the geometries do not align, 'ShowRenderWindowWarning' is called - * and a warning message is added to the top of this plugin view. - * - * @param event The observed mitk::SegmentationInteractionEvent. - */ - void ValidateRendererGeometry(const itk::EventObject& event); void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartInputChanged(mitk::IRenderWindowPart* renderWindowPart) override; void OnPreferencesChanged(const mitk::IPreferences* prefs) override; void NodeAdded(const mitk::DataNode* node) override; void NodeRemoved(const mitk::DataNode* node) override; void OnAnySelectionChanged(); // make sure all images / segmentations look according to the user preference settings void ApplyDisplayOptions(); // decorates a DataNode according to the user preference settings void ApplyDisplayOptions(mitk::DataNode* node); void ApplySelectionMode(); void ApplySelectionModeOnReferenceNode(); void ApplySelectionModeOnWorkingNode(); void ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate); // If a contourmarker is selected, the plane in the related widget will be reoriented according to the marker`s geometry void OnContourMarkerSelected(const mitk::DataNode* node); void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) override; void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource&, int hotspotX, int hotspotY); void UpdateGUI(); void ValidateSelectionInput(); void UpdateWarningLabel(QString text); std::string GetDefaultLabelSetPreset() const; mitk::LabelSetImage* GetCurrentSegmentation() const; QWidget* m_Parent; Ui::QmitkSegmentationViewControls* m_Controls; mitk::IRenderWindowPart* m_RenderWindowPart; mitk::ToolManager* m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; typedef std::map NodeTagMapType; NodeTagMapType m_WorkingDataObserverTags; NodeTagMapType m_ReferenceDataObserverTags; unsigned int m_RenderingManagerObserverTag; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_DrawOutline; bool m_SelectionMode; bool m_MouseCursorSet; QString m_LabelSetPresetPreference; bool m_DefaultLabelNaming; bool m_SelectionChangeIsAlreadyBeingHandled; }; #endif